diff --git a/_freeze/ancova/execute-results/html.json b/_freeze/ancova/execute-results/html.json index e8e5ee1..43e72fc 100644 --- a/_freeze/ancova/execute-results/html.json +++ b/_freeze/ancova/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "39e30dfec49e66a4ffc46ebb3db688c2", + "hash": "1a46e4f6302227d07b496793324ee69b", "result": { - "markdown": "---\ntitle: \"Analysis of Covariance\"\n---\n\n\n\n\n## Analysis of covariance\n\n\n* ANOVA: explanatory variables categorical (divide data into groups)\n\n* traditionally, analysis of covariance has categorical $x$'s plus one numerical $x$ (\"covariate\") to be adjusted for.\n\n* `lm` handles this too.\n\n* Simple example: two treatments (drugs) (`a` and `b`), with before and after scores. \n\n\n* Does knowing before score and/or treatment help to predict after score?\n\n* Is after score different by treatment/before score?\n\n\n\n\n\n## Data\n\nTreatment, before, after: \n\n\n\\scriptsize\n```\na 5 20\na 10 23\na 12 30\na 9 25\na 23 34\na 21 40\na 14 27\na 18 38\na 6 24\na 13 31\nb 7 19\nb 12 26\nb 27 33\nb 24 35\nb 18 30\nb 22 31\nb 26 34\nb 21 28\nb 14 23\nb 9 22\n```\n\\normalsize\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\n```\n:::\n\n\nthe last of these for predictions.\n\n## Read in data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- \"http://ritsokiguess.site/datafiles/ancova.txt\"\nprepost <- read_delim(url, \" \")\nprepost\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n## Making a plot\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(prepost, aes(x = before, y = after, colour = drug)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-revealjs/ancova-plot-1.png){width=960}\n:::\n:::\n\n\n\n\n## Comments \n\n* As before score goes up, after score goes up.\n\n* Red points (drug A) generally above blue points (drug B), for\ncomparable before score.\n\n* Suggests before score effect *and* drug effect.\n\n\n## The means\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost %>%\n group_by(drug) %>%\n summarize(\n before_mean = mean(before),\n after_mean = mean(after)\n )\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n* Mean \"after\" score slightly higher for treatment A.\n\n* Mean \"before\" score much higher for treatment B.\n\n* Greater *improvement* on treatment A. \n\n\n\n## Testing for interaction\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.1 <- lm(after ~ before * drug, data = prepost)\nanova(prepost.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n* Interaction not significant. Will remove later.\n\n## Predictions\n\nSet up values to predict for:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before after \n Length:20 Min. : 5.00 Min. :19.00 \n Class :character 1st Qu.: 9.75 1st Qu.:23.75 \n Mode :character Median :14.00 Median :29.00 \n Mean :15.55 Mean :28.65 \n 3rd Qu.:21.25 3rd Qu.:33.25 \n Max. :27.00 Max. :40.00 \n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(before = c(9.75, 14, 21.25), \n drug = c(\"a\", \"b\"), model = prepost.1)\n```\n:::\n\n\n## and then\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.1, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Predictions (with interaction included), plotted\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(model = prepost.1, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-revealjs/unnamed-chunk-4-1.png){width=960}\n:::\n:::\n\n\nLines almost parallel, but not quite.\n\n\n\n## Taking out interaction\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.2 <- update(prepost.1, . ~ . - before:drug)\nanova(prepost.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\\normalsize\n\n\n* Take out non-significant interaction.\n\n* `before` and `drug` strongly significant.\n\n* Do predictions again and plot them.\n\n## Predictions\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.2, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Plot of predicted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(prepost.2, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-revealjs/unnamed-chunk-6-1.png){width=960}\n:::\n:::\n\n\nThis time the lines are *exactly* parallel. No-interaction model forces them\nto have the same slope. \n\n\n\n## Different look at model output\n\n\n* `anova(prepost.2)` tests for significant effect of\nbefore score and of drug, but doesn't help with interpretation.\n\n* `summary(prepost.2)` views as regression with slopes:\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = after ~ before + drug, data = prepost)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.6348 -2.5099 -0.2038 1.8871 4.7453 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 18.3600 1.5115 12.147 8.35e-10 ***\nbefore 0.8275 0.0955 8.665 1.21e-07 ***\ndrugb -5.1547 1.2876 -4.003 0.000921 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.682 on 17 degrees of freedom\nMultiple R-squared: 0.817,\tAdjusted R-squared: 0.7955 \nF-statistic: 37.96 on 2 and 17 DF, p-value: 5.372e-07\n```\n:::\n:::\n\n\n\\normalsize \n\n\n\n\n## Understanding those slopes\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(prepost.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n\n\n* `before` ordinary numerical variable; `drug`\ncategorical. \n\n* `lm` uses first category `druga` as baseline.\n\n* Intercept is prediction of after score for before score 0 and\n*drug A*.\n\n* `before` slope is predicted change in after score when\nbefore score increases by 1 (usual slope)\n\n* Slope for `drugb` is *change* in predicted after\nscore for being on drug B rather than drug A. Same for *any*\nbefore score (no interaction).\n\n\n\n## Summary\n\n\n* ANCOVA model: fits different regression line for each group,\npredicting response from covariate.\n\n* ANCOVA model with interaction between factor and covariate\nallows different slopes for each line.\n\n* Sometimes those lines can cross over!\n\n* If interaction not significant, take out. Lines then parallel.\n\n* With parallel lines, groups have consistent effect regardless\nof value of covariate.\n\n", + "markdown": "---\ntitle: \"Analysis of Covariance\"\n---\n\n\n\n\n## Analysis of covariance\n\n\n* ANOVA: explanatory variables categorical (divide data into groups)\n\n* traditionally, analysis of covariance has categorical $x$'s plus one numerical $x$ (\"covariate\") to be adjusted for.\n\n* `lm` handles this too.\n\n* Simple example: two treatments (drugs) (`a` and `b`), with before and after scores. \n\n\n* Does knowing before score and/or treatment help to predict after score?\n\n* Is after score different by treatment/before score?\n\n\n\n\n\n## Data\n\nTreatment, before, after: \n\n\n\\scriptsize\n```\na 5 20\na 10 23\na 12 30\na 9 25\na 23 34\na 21 40\na 14 27\na 18 38\na 6 24\na 13 31\nb 7 19\nb 12 26\nb 27 33\nb 24 35\nb 18 30\nb 22 31\nb 26 34\nb 21 28\nb 14 23\nb 9 22\n```\n\\normalsize\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\n```\n:::\n\n\nthe last of these for predictions.\n\n## Read in data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- \"http://ritsokiguess.site/datafiles/ancova.txt\"\nprepost <- read_delim(url, \" \")\nprepost\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n## Making a plot\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(prepost, aes(x = before, y = after, colour = drug)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-revealjs/ancova-plot-1.png){width=960}\n:::\n:::\n\n\n\n\n## Comments \n\n* As before score goes up, after score goes up.\n\n* Red points (drug A) generally above blue points (drug B), for\ncomparable before score.\n\n* Suggests before score effect *and* drug effect.\n\n\n## The means\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost %>%\n group_by(drug) %>%\n summarize(\n before_mean = mean(before),\n after_mean = mean(after)\n )\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n* Mean \"after\" score slightly higher for treatment A.\n\n* Mean \"before\" score much higher for treatment B.\n\n* Greater *improvement* on treatment A. \n\n\n\n## Testing for interaction\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.1 <- lm(after ~ before * drug, data = prepost)\nanova(prepost.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n* Interaction not significant. Will remove later.\n\n## Predictions\n\nSet up values to predict for:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before after \n Length:20 Min. : 5.00 Min. :19.00 \n Class :character 1st Qu.: 9.75 1st Qu.:23.75 \n Mode :character Median :14.00 Median :29.00 \n Mean :15.55 Mean :28.65 \n 3rd Qu.:21.25 3rd Qu.:33.25 \n Max. :27.00 Max. :40.00 \n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(before = c(9.75, 14, 21.25), \n drug = c(\"a\", \"b\"), model = prepost.1)\n```\n:::\n\n\n## and then\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.1, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Predictions (with interaction included), plotted\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(model = prepost.1, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-revealjs/unnamed-chunk-4-1.png){width=960}\n:::\n:::\n\n\nLines almost parallel, but not quite.\n\n\n\n## Taking out interaction\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.2 <- update(prepost.1, . ~ . - before:drug)\nanova(prepost.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\\normalsize\n\n\n* Take out non-significant interaction.\n\n* `before` and `drug` strongly significant.\n\n* Do predictions again and plot them.\n\n## Predictions\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.2, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Plot of predicted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(prepost.2, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-revealjs/unnamed-chunk-6-1.png){width=960}\n:::\n:::\n\n\nThis time the lines are *exactly* parallel. No-interaction model forces them\nto have the same slope. \n\n\n\n## Different look at model output\n\n\n* `anova(prepost.2)` tests for significant effect of\nbefore score and of drug, but doesn't help with interpretation.\n\n* `summary(prepost.2)` views as regression with slopes:\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = after ~ before + drug, data = prepost)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.6348 -2.5099 -0.2038 1.8871 4.7453 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 18.3600 1.5115 12.147 8.35e-10 ***\nbefore 0.8275 0.0955 8.665 1.21e-07 ***\ndrugb -5.1547 1.2876 -4.003 0.000921 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.682 on 17 degrees of freedom\nMultiple R-squared: 0.817,\tAdjusted R-squared: 0.7955 \nF-statistic: 37.96 on 2 and 17 DF, p-value: 5.372e-07\n```\n:::\n:::\n\n\n\\normalsize \n\n\n\n\n## Understanding those slopes\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(prepost.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n\n\n* `before` ordinary numerical variable; `drug`\ncategorical. \n\n* `lm` uses first category `druga` as baseline.\n\n* Intercept is prediction of after score for before score 0 and\n*drug A*.\n\n* `before` slope is predicted change in after score when\nbefore score increases by 1 (usual slope)\n\n* Slope for `drugb` is *change* in predicted after\nscore for being on drug B rather than drug A. Same for *any*\nbefore score (no interaction).\n\n\n\n## Summary\n\n\n* ANCOVA model: fits different regression line for each group,\npredicting response from covariate.\n\n* ANCOVA model with interaction between factor and covariate\nallows different slopes for each line.\n\n* Sometimes those lines can cross over!\n\n* If interaction not significant, take out. Lines then parallel.\n\n* With parallel lines, groups have consistent effect regardless\nof value of covariate.\n\n", "supporting": [ "ancova_files/figure-revealjs" ], diff --git a/_freeze/ancova/execute-results/tex.json b/_freeze/ancova/execute-results/tex.json index 89a1cf8..e0e3a78 100644 --- a/_freeze/ancova/execute-results/tex.json +++ b/_freeze/ancova/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "39e30dfec49e66a4ffc46ebb3db688c2", + "hash": "1a46e4f6302227d07b496793324ee69b", "result": { - "markdown": "---\ntitle: \"Analysis of Covariance\"\n---\n\n\n\n\n\n## Analysis of covariance\n\n\n* ANOVA: explanatory variables categorical (divide data into groups)\n\n* traditionally, analysis of covariance has categorical $x$'s plus one numerical $x$ (\"covariate\") to be adjusted for.\n\n* `lm` handles this too.\n\n* Simple example: two treatments (drugs) (`a` and `b`), with before and after scores. \n\n\n* Does knowing before score and/or treatment help to predict after score?\n\n* Is after score different by treatment/before score?\n\n\n\n\n\n## Data\n\nTreatment, before, after: \n\n\n\\scriptsize\n```\na 5 20\na 10 23\na 12 30\na 9 25\na 23 34\na 21 40\na 14 27\na 18 38\na 6 24\na 13 31\nb 7 19\nb 12 26\nb 27 33\nb 24 35\nb 18 30\nb 22 31\nb 26 34\nb 21 28\nb 14 23\nb 9 22\n```\n\\normalsize\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\n```\n:::\n\n\n\nthe last of these for predictions.\n\n## Read in data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- \"http://ritsokiguess.site/datafiles/ancova.txt\"\nprepost <- read_delim(url, \" \")\nprepost\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n drug before after\n \n 1 a 5 20\n 2 a 10 23\n 3 a 12 30\n 4 a 9 25\n 5 a 23 34\n 6 a 21 40\n 7 a 14 27\n 8 a 18 38\n 9 a 6 24\n10 a 13 31\n11 b 7 19\n12 b 12 26\n13 b 27 33\n14 b 24 35\n15 b 18 30\n16 b 22 31\n17 b 26 34\n18 b 21 28\n19 b 14 23\n20 b 9 22\n```\n:::\n:::\n\n\n\n\n\n## Making a plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(prepost, aes(x = before, y = after, colour = drug)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-beamer/ancova-plot-1.pdf)\n:::\n:::\n\n\n\n\n\n## Comments \n\n* As before score goes up, after score goes up.\n\n* Red points (drug A) generally above blue points (drug B), for\ncomparable before score.\n\n* Suggests before score effect *and* drug effect.\n\n\n## The means\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost %>%\n group_by(drug) %>%\n summarize(\n before_mean = mean(before),\n after_mean = mean(after)\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 3\n drug before_mean after_mean\n \n1 a 13.1 29.2\n2 b 18 28.1\n```\n:::\n:::\n\n\n \n\n\n* Mean \"after\" score slightly higher for treatment A.\n\n* Mean \"before\" score much higher for treatment B.\n\n* Greater *improvement* on treatment A. \n\n\n\n## Testing for interaction\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.1 <- lm(after ~ before * drug, data = prepost)\nanova(prepost.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nResponse: after\n Df Sum Sq Mean Sq F value Pr(>F) \nbefore 1 430.92 430.92 62.6894 6.34e-07 ***\ndrug 1 115.31 115.31 16.7743 0.0008442 ***\nbefore:drug 1 12.34 12.34 1.7948 0.1990662 \nResiduals 16 109.98 6.87 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n \n\n\n* Interaction not significant. Will remove later.\n\n## Predictions\n\nSet up values to predict for:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before after \n Length:20 Min. : 5.00 Min. :19.00 \n Class :character 1st Qu.: 9.75 1st Qu.:23.75 \n Mode :character Median :14.00 Median :29.00 \n Mean :15.55 Mean :28.65 \n 3rd Qu.:21.25 3rd Qu.:33.25 \n Max. :27.00 Max. :40.00 \n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(before = c(9.75, 14, 21.25), \n drug = c(\"a\", \"b\"), model = prepost.1)\n```\n:::\n\n\n\n## and then\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.1, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before estimate\n1 a 9.75 25.93250\n2 b 9.75 22.14565\n3 a 14.00 30.07784\n4 b 14.00 25.21304\n5 a 21.25 37.14929\n6 b 21.25 30.44565\n```\n:::\n:::\n\n\n\\normalsize\n\n## Predictions (with interaction included), plotted\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(model = prepost.1, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-beamer/unnamed-chunk-4-1.pdf)\n:::\n:::\n\n\n\nLines almost parallel, but not quite.\n\n\n\n## Taking out interaction\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.2 <- update(prepost.1, . ~ . - before:drug)\nanova(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nResponse: after\n Df Sum Sq Mean Sq F value Pr(>F) \nbefore 1 430.92 430.92 59.890 5.718e-07 ***\ndrug 1 115.31 115.31 16.025 0.0009209 ***\nResiduals 17 122.32 7.20 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n \n\\normalsize\n\n\n* Take out non-significant interaction.\n\n* `before` and `drug` strongly significant.\n\n* Do predictions again and plot them.\n\n## Predictions\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.2, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before estimate\n1 a 9.75 26.42794\n2 b 9.75 21.27328\n3 a 14.00 29.94473\n4 b 14.00 24.79007\n5 a 21.25 35.94397\n6 b 21.25 30.78931\n```\n:::\n:::\n\n\n\n\n## Plot of predicted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(prepost.2, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-beamer/unnamed-chunk-6-1.pdf)\n:::\n:::\n\n\n\nThis time the lines are *exactly* parallel. No-interaction model forces them\nto have the same slope. \n\n\n\n## Different look at model output\n\n\n* `anova(prepost.2)` tests for significant effect of\nbefore score and of drug, but doesn't help with interpretation.\n\n* `summary(prepost.2)` views as regression with slopes:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = after ~ before + drug, data = prepost)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.6348 -2.5099 -0.2038 1.8871 4.7453 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 18.3600 1.5115 12.147 8.35e-10 ***\nbefore 0.8275 0.0955 8.665 1.21e-07 ***\ndrugb -5.1547 1.2876 -4.003 0.000921 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.682 on 17 degrees of freedom\nMultiple R-squared: 0.817,\tAdjusted R-squared: 0.7955 \nF-statistic: 37.96 on 2 and 17 DF, p-value: 5.372e-07\n```\n:::\n:::\n\n\n\n\\normalsize \n\n\n\n\n## Understanding those slopes\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 18.4 1.51 12.1 8.35e-10\n2 before 0.827 0.0955 8.66 1.21e- 7\n3 drugb -5.15 1.29 -4.00 9.21e- 4\n```\n:::\n:::\n\n\n\n\\normalsize\n\n\n\n* `before` ordinary numerical variable; `drug`\ncategorical. \n\n* `lm` uses first category `druga` as baseline.\n\n* Intercept is prediction of after score for before score 0 and\n*drug A*.\n\n* `before` slope is predicted change in after score when\nbefore score increases by 1 (usual slope)\n\n* Slope for `drugb` is *change* in predicted after\nscore for being on drug B rather than drug A. Same for *any*\nbefore score (no interaction).\n\n\n\n## Summary\n\n\n* ANCOVA model: fits different regression line for each group,\npredicting response from covariate.\n\n* ANCOVA model with interaction between factor and covariate\nallows different slopes for each line.\n\n* Sometimes those lines can cross over!\n\n* If interaction not significant, take out. Lines then parallel.\n\n* With parallel lines, groups have consistent effect regardless\nof value of covariate.\n\n", + "markdown": "---\ntitle: \"Analysis of Covariance\"\n---\n\n\n\n\n\n## Analysis of covariance\n\n\n* ANOVA: explanatory variables categorical (divide data into groups)\n\n* traditionally, analysis of covariance has categorical $x$'s plus one numerical $x$ (\"covariate\") to be adjusted for.\n\n* `lm` handles this too.\n\n* Simple example: two treatments (drugs) (`a` and `b`), with before and after scores. \n\n\n* Does knowing before score and/or treatment help to predict after score?\n\n* Is after score different by treatment/before score?\n\n\n\n\n\n## Data\n\nTreatment, before, after: \n\n\n\\scriptsize\n```\na 5 20\na 10 23\na 12 30\na 9 25\na 23 34\na 21 40\na 14 27\na 18 38\na 6 24\na 13 31\nb 7 19\nb 12 26\nb 27 33\nb 24 35\nb 18 30\nb 22 31\nb 26 34\nb 21 28\nb 14 23\nb 9 22\n```\n\\normalsize\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\n```\n:::\n\n\n\nthe last of these for predictions.\n\n## Read in data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- \"http://ritsokiguess.site/datafiles/ancova.txt\"\nprepost <- read_delim(url, \" \")\nprepost\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n drug before after\n \n 1 a 5 20\n 2 a 10 23\n 3 a 12 30\n 4 a 9 25\n 5 a 23 34\n 6 a 21 40\n 7 a 14 27\n 8 a 18 38\n 9 a 6 24\n10 a 13 31\n11 b 7 19\n12 b 12 26\n13 b 27 33\n14 b 24 35\n15 b 18 30\n16 b 22 31\n17 b 26 34\n18 b 21 28\n19 b 14 23\n20 b 9 22\n```\n:::\n:::\n\n\n\n\n\n## Making a plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(prepost, aes(x = before, y = after, colour = drug)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-beamer/ancova-plot-1.pdf)\n:::\n:::\n\n\n\n\n\n## Comments \n\n* As before score goes up, after score goes up.\n\n* Red points (drug A) generally above blue points (drug B), for\ncomparable before score.\n\n* Suggests before score effect *and* drug effect.\n\n\n## The means\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost %>%\n group_by(drug) %>%\n summarize(\n before_mean = mean(before),\n after_mean = mean(after)\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 3\n drug before_mean after_mean\n \n1 a 13.1 29.2\n2 b 18 28.1\n```\n:::\n:::\n\n\n \n\n\n* Mean \"after\" score slightly higher for treatment A.\n\n* Mean \"before\" score much higher for treatment B.\n\n* Greater *improvement* on treatment A. \n\n\n\n## Testing for interaction\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.1 <- lm(after ~ before * drug, data = prepost)\nanova(prepost.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nResponse: after\n Df Sum Sq Mean Sq F value Pr(>F) \nbefore 1 430.92 430.92 62.6894 6.34e-07 ***\ndrug 1 115.31 115.31 16.7743 0.0008442 ***\nbefore:drug 1 12.34 12.34 1.7948 0.1990662 \nResiduals 16 109.98 6.87 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n \n\n\n* Interaction not significant. Will remove later.\n\n## Predictions\n\nSet up values to predict for:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before after \n Length:20 Min. : 5.00 Min. :19.00 \n Class :character 1st Qu.: 9.75 1st Qu.:23.75 \n Mode :character Median :14.00 Median :29.00 \n Mean :15.55 Mean :28.65 \n 3rd Qu.:21.25 3rd Qu.:33.25 \n Max. :27.00 Max. :40.00 \n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(before = c(9.75, 14, 21.25), \n drug = c(\"a\", \"b\"), model = prepost.1)\n```\n:::\n\n\n\n## and then\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.1, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before estimate\n1 a 9.75 25.93250\n2 b 9.75 22.14565\n3 a 14.00 30.07784\n4 b 14.00 25.21304\n5 a 21.25 37.14929\n6 b 21.25 30.44565\n```\n:::\n:::\n\n\n\\normalsize\n\n## Predictions (with interaction included), plotted\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(model = prepost.1, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-beamer/unnamed-chunk-4-1.pdf)\n:::\n:::\n\n\n\nLines almost parallel, but not quite.\n\n\n\n## Taking out interaction\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprepost.2 <- update(prepost.1, . ~ . - before:drug)\nanova(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nResponse: after\n Df Sum Sq Mean Sq F value Pr(>F) \nbefore 1 430.92 430.92 59.890 5.718e-07 ***\ndrug 1 115.31 115.31 16.025 0.0009209 ***\nResiduals 17 122.32 7.20 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n \n\\normalsize\n\n\n* Take out non-significant interaction.\n\n* `before` and `drug` strongly significant.\n\n* Do predictions again and plot them.\n\n## Predictions\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(prepost.2, newdata = new)) %>% \n select(drug, before, estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n drug before estimate\n1 a 9.75 26.42794\n2 b 9.75 21.27328\n3 a 14.00 29.94473\n4 b 14.00 24.79007\n5 a 21.25 35.94397\n6 b 21.25 30.78931\n```\n:::\n:::\n\n\n\n\n## Plot of predicted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(prepost.2, condition = c(\"before\", \"drug\"))\n```\n\n::: {.cell-output-display}\n![](ancova_files/figure-beamer/unnamed-chunk-6-1.pdf)\n:::\n:::\n\n\n\nThis time the lines are *exactly* parallel. No-interaction model forces them\nto have the same slope. \n\n\n\n## Different look at model output\n\n\n* `anova(prepost.2)` tests for significant effect of\nbefore score and of drug, but doesn't help with interpretation.\n\n* `summary(prepost.2)` views as regression with slopes:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = after ~ before + drug, data = prepost)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.6348 -2.5099 -0.2038 1.8871 4.7453 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 18.3600 1.5115 12.147 8.35e-10 ***\nbefore 0.8275 0.0955 8.665 1.21e-07 ***\ndrugb -5.1547 1.2876 -4.003 0.000921 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.682 on 17 degrees of freedom\nMultiple R-squared: 0.817,\tAdjusted R-squared: 0.7955 \nF-statistic: 37.96 on 2 and 17 DF, p-value: 5.372e-07\n```\n:::\n:::\n\n\n\n\\normalsize \n\n\n\n\n## Understanding those slopes\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(prepost.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 18.4 1.51 12.1 8.35e-10\n2 before 0.827 0.0955 8.66 1.21e- 7\n3 drugb -5.15 1.29 -4.00 9.21e- 4\n```\n:::\n:::\n\n\n\n\\normalsize\n\n\n\n* `before` ordinary numerical variable; `drug`\ncategorical. \n\n* `lm` uses first category `druga` as baseline.\n\n* Intercept is prediction of after score for before score 0 and\n*drug A*.\n\n* `before` slope is predicted change in after score when\nbefore score increases by 1 (usual slope)\n\n* Slope for `drugb` is *change* in predicted after\nscore for being on drug B rather than drug A. Same for *any*\nbefore score (no interaction).\n\n\n\n## Summary\n\n\n* ANCOVA model: fits different regression line for each group,\npredicting response from covariate.\n\n* ANCOVA model with interaction between factor and covariate\nallows different slopes for each line.\n\n* Sometimes those lines can cross over!\n\n* If interaction not significant, take out. Lines then parallel.\n\n* With parallel lines, groups have consistent effect regardless\nof value of covariate.\n\n", "supporting": [ "ancova_files/figure-beamer" ], diff --git a/_freeze/ancova/figure-beamer/ancova-plot-1.pdf b/_freeze/ancova/figure-beamer/ancova-plot-1.pdf index 4634a40..9451afc 100644 Binary files a/_freeze/ancova/figure-beamer/ancova-plot-1.pdf and b/_freeze/ancova/figure-beamer/ancova-plot-1.pdf differ diff --git a/_freeze/ancova/figure-beamer/unnamed-chunk-4-1.pdf b/_freeze/ancova/figure-beamer/unnamed-chunk-4-1.pdf index a15d6f6..217fc6a 100644 Binary files a/_freeze/ancova/figure-beamer/unnamed-chunk-4-1.pdf and b/_freeze/ancova/figure-beamer/unnamed-chunk-4-1.pdf differ diff --git a/_freeze/ancova/figure-beamer/unnamed-chunk-6-1.pdf b/_freeze/ancova/figure-beamer/unnamed-chunk-6-1.pdf index 4530a59..3d2ae2c 100644 Binary files a/_freeze/ancova/figure-beamer/unnamed-chunk-6-1.pdf and b/_freeze/ancova/figure-beamer/unnamed-chunk-6-1.pdf differ diff --git a/_freeze/ancova/figure-revealjs/unnamed-chunk-4-1.png b/_freeze/ancova/figure-revealjs/unnamed-chunk-4-1.png index b6f4101..99604e4 100644 Binary files a/_freeze/ancova/figure-revealjs/unnamed-chunk-4-1.png and b/_freeze/ancova/figure-revealjs/unnamed-chunk-4-1.png differ diff --git a/_freeze/ancova/figure-revealjs/unnamed-chunk-6-1.png b/_freeze/ancova/figure-revealjs/unnamed-chunk-6-1.png index d0f2084..5ec9ff2 100644 Binary files a/_freeze/ancova/figure-revealjs/unnamed-chunk-6-1.png and b/_freeze/ancova/figure-revealjs/unnamed-chunk-6-1.png differ diff --git a/_freeze/asphalt/execute-results/html.json b/_freeze/asphalt/execute-results/html.json index f838b58..b5bc203 100644 --- a/_freeze/asphalt/execute-results/html.json +++ b/_freeze/asphalt/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "25e7e208e7eefbed7de9dbae587937fa", + "hash": "6c8eba076b49aa7fbc7a066250547c9f", "result": { - "markdown": "---\ntitle: \"Case study: asphalt\"\n---\n\n\n## The asphalt data\n- 31 asphalt pavements prepared under different conditions. How does\nquality of pavement depend on these?\n- Variables:\n - `pct.a.surf` Percentage of asphalt in surface layer\n - `pct.a.base` Percentage of asphalt in base layer\n - `fines` Percentage of fines in surface layer\n - `voids` Percentage of voids in surface layer\n - `rut.depth` Change in rut depth per million vehicle passes\n - `viscosity` Viscosity of asphalt\n - `run` 2 data collection periods: 1 for run 1, 0 for run 2.\n- `rut.depth` response. Depends on other variables, how?\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(leaps)\n```\n:::\n\n\nMake sure to load `MASS` before `tidyverse` (for annoying technical reasons).\n\n## Getting set up \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/asphalt.txt\"\nasphalt <- read_delim(my_url, \" \")\n```\n:::\n\n\n- Quantitative variables with one response: multiple regression.\n- Some issues here that don’t come up in “simple” regression; handle as\nwe go. (STAB27/STAC67 ideas.)\n\n## The data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Plotting response “rut depth” against everything else\n\nSame idea as for plotting separate predictions on one plot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n pivot_longer(\n -rut.depth,\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = rut.depth)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n“collect all the x-variables together into one column called x, with another\ncolumn xname saying which x they were, then plot these x’s against\nrut.depth, a separate facet for each x-variable.”\n\nI saved this graph to plot later (on the next page).\n\n## The plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-5-1.png){width=960}\n:::\n:::\n\n\n## Interpreting the plots\n- One plot of rut depth against each of the six other variables.\n- Get rough idea of what’s going on.\n- Trends mostly weak.\n- `viscosity` has strong but non-linear trend.\n- `run` has effect but variability bigger when run is 1.\n- Weak but downward trend for `voids`.\n- Non-linearity of `rut.depth`-`viscosity` relationship should concern\nus.\n\n## Log of `viscosity`: more nearly linear?\n\n- Take this back to asphalt engineer: suggests log of `viscosity`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(asphalt, aes(y = rut.depth, x = log(viscosity))) +\n geom_point() + geom_smooth(se = F)\n```\n:::\n\n\n(plot overleaf)\n\n## Rut depth against log-viscosity\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-6-1.png){width=960}\n:::\n:::\n\n\n## Comments and next steps\n- Not very linear, but better than before.\n- In multiple regression, hard to guess which x’s affect response. So\ntypically start by predicting from everything else.\n- Model formula has response on left, squiggle, explanatories on right\njoined by plusses:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 <- lm(rut.depth ~ pct.a.surf + pct.a.base + fines +\n voids + log(viscosity) + run, data = asphalt)\n```\n:::\n\n\n## Regression output: `summary(rut.1)` or:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(rut.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ntidy(rut.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Comments\n- R-squared 81%, not so bad. \n- P-value in `glance` asserts that something helping to predict\nrut.depth.\n- Table of coefficients says `log(viscosity)`.\n- But confused by clearly non-significant variables: remove those to get\nclearer picture of what is helpful.\n- Before we do anything, look at residual plots:\n - (a) of residuals against fitted values (as usual)\n - (b) of residuals against each explanatory.\n- Problem fixes:\n - with (a): fix response variable; \n - with some plots in (b): fix those explanatory variables.\n\n## Plot fitted values against residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-9-1.png){width=960}\n:::\n:::\n\n\n## Plotting residuals against $x$ variables\n- Problem here is that residuals are in the fitted model, and the\nobserved $x$-values are in the original data frame `asphalt`. \n- Package broom contains a function `augment` that combines these two\ntogether so that they can later be plotted: start with a model first, and then augment with a\ndata frame:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 %>% augment(asphalt) -> rut.1a\n```\n:::\n\n\n\n## What does rut.1a contain?\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnames(rut.1a)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] \"pct.a.surf\" \"pct.a.base\" \"fines\" \"voids\" \"rut.depth\" \n [6] \"viscosity\" \"run\" \".fitted\" \".resid\" \".hat\" \n[11] \".sigma\" \".cooksd\" \".std.resid\"\n```\n:::\n:::\n\n\n- all the stuff in original data frame, plus:\n- quantities from regression (starting with a dot)\n\n\n## Plotting residuals against $x$-variables \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1a %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = .resid)) +\n geom_point() + facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n## The plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-14-1.png){width=960}\n:::\n:::\n\n\n## Comments\n- There is serious curve in plot of residuals vs. fitted values. Suggests a\ntransformation of $y$. \n- The residuals-vs-$x$’s plots don’t show any serious trends. Worst\nprobably that potential curve against log-viscosity.\n- Also, large positive residual, 10, that shows up on all plots. Perhaps\ntransformation of $y$ will help with this too.\n- If residual-fitted plot OK, but some residual-$x$ plots not, try\ntransforming those $x$’s, eg. by adding $x^2$ to help with curve.\n\n## Which transformation?\n- Best way: consult with person who brought you the data.\n- Can’t do that here!\n- No idea what transformation would be good.\n- Let data choose: “Box-Cox transformation”.\n- Scale is that of “ladder of powers”: power transformation, but 0 is\nlog.\n\n\n## Running Box-Cox\n\nFrom package `MASS`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(rut.depth ~ pct.a.surf + pct.a.base + fines + voids +\n log(viscosity) + run, data = asphalt)\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-15-1.png){width=960}\n:::\n:::\n\n\n## Comments on Box-Cox plot\n- $\\lambda$ represents power to transform $y$ with.\n- Best single choice of transformation parameter $\\lambda$ is peak of curve,\nclose to 0.\n- Vertical dotted lines give CI for $\\lambda$, about (−0.05, 0.2).\n- $\\lambda = 0$ means “log”.\n- Narrowness of confidence interval mean that these not supported by\ndata:\n - No transformation ($\\lambda = 1$)\n - Square root ($\\lambda = 0.5$)\n - Reciprocal ($\\lambda = −1$).\n\n## Relationships with explanatories\n- As before: plot response (now `log(rut.depth)`) against other\nexplanatory variables, all in one shot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(y = log(rut.depth), x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g3\n```\n:::\n\n\n## The new plots\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng3\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-17-1.png){width=960}\n:::\n:::\n\n\n## Modelling with transformed response\n- These trends look pretty straight, especially with `log.viscosity`.\n- Values of `log.rut.depth` for each `run` have same spread.\n- Other trends weak, but are straight if they exist.\n- Start modelling from the beginning again.\n- Model `log.rut.depth` in terms of everything else, see what can be\nremoved:\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.2 <- lm(log(rut.depth) ~ pct.a.surf + pct.a.base +\n fines + voids + log(viscosity) + run, data = asphalt)\n```\n:::\n\n\n- use `tidy` from `broom` to display just the coefficients.\n\n## Output\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Taking out everything non-significant\n- Try: remove everything but pct.a.surf and log.viscosity:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.3 <- lm(log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)\n```\n:::\n\n\\normalsize\n\n\\footnotesize\n- Check that removing all those variables wasn’t too much:\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(rut.3, rut.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n- $H_0$ : two models equally good; $H_a$ : bigger model better.\n- Null not rejected here; small model as good as the big one, so prefer\nsimpler smaller model `rut.3`.\n\n## Find the largest P-value by eye:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Largest P-value is 0.78 for `pct.a.base`, not significant.\n- So remove this first, re-fit and re-assess.\n- Or, as over.\n\n## Get the computer to find the largest P-value for you\n\n- Output from `tidy` is itself a data frame, thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Largest P-value at the bottom.\n\n## Take out `pct.a.base`\n\n- Copy and paste the `lm` code and remove what you're removing:\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- lm(log(rut.depth) ~ pct.a.surf + fines + voids + \n log(viscosity) + run, data = asphalt)\ntidy(rut.4) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n- `fines` is next to go, P-value 0.32.\n\n## “Update”\n\nAnother way to do the same thing:\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- update(rut.2, . ~ . - pct.a.base)\ntidy(rut.4) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Again, `fines` is the one to go. (Output identical as it should be.)\n\n## Take out fines:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.5 <- update(rut.4, . ~ . - fines)\ntidy(rut.5) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nCan’t take out intercept, so `run`, with P-value 0.36, goes next.\n\n## Take out run:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.6 <- update(rut.5, . ~ . - run)\ntidy(rut.6) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nAgain, can’t take out intercept, so largest P-value is for `voids`, 0.044. But\nthis is significant, so we shouldn’t remove `voids`.\n\n## Comments\n- Here we stop: `pct.a.surf`, `voids` and `log.viscosity` would all\nmake fit significantly worse if removed. So they stay.\n- Different final result from taking things out one at a time (top), than\nby taking out 4 at once (bottom):\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoef(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf voids log(viscosity) \n -1.0207945 0.5554686 0.2447934 -0.6464911 \n```\n:::\n\n```{.r .cell-code}\ncoef(rut.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf log(viscosity) \n 0.9001389 0.3911481 -0.6185628 \n```\n:::\n:::\n\n\n- Point: Can make difference which way we go.\n\n## Comments on variable selection\n- Best way to decide which $x$’s belong: expert knowledge: which of\nthem should be important.\n- Best automatic method: what we did, “backward selection”.\n- Do not learn about “stepwise regression”! [**eg. here**](https://towardsdatascience.com/stopping-stepwise-why-stepwise-selection-is-bad-and-what-you-should-use-instead-90818b3f52df)\n- R has function `step` that does backward selection, like this:\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(rut.2, direction = \"backward\", test = \"F\")\n```\n:::\n\n\nGets same answer as we did (by removing least significant x). \n\n- Removing non-significant $x$’s may remove interesting ones whose\nP-values happened not to reach 0.05. Consider using less stringent\ncutoff like 0.20 or even bigger.\n- Can also fit all possible regressions, as over (may need to do\n`install.packages(\"leaps\")` first).\n\n## All possible regressions (output over)\n\nUses package `leaps`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nleaps <- regsubsets(log(rut.depth) ~ pct.a.surf + \n pct.a.base + fines + voids + \n log(viscosity) + run, \n data = asphalt, nbest = 2)\ns <- summary(leaps)\nwith(s, data.frame(rsq, outmat)) -> d\n```\n:::\n\n\n## The output\n\n\n::: {.cell}\n\n:::\n\n\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rownames_to_column(\"model\") %>% arrange(desc(rsq))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n::: {.cell}\n\n:::\n\n\n\n## Comments\n- Problem: even adding a worthless x increases R-squared. So try for\nline where R-squared stops increasing “too much”, eg. top line (just\nlog.viscosity), first 3-variable line (backwards-elimination model).\nHard to judge.\n- One solution (STAC67): adjusted R-squared, where adding worthless\nvariable makes it go down.\n- `data.frame` rather than `tibble` because there are several columns in\n`outmat`. \n\n## All possible regressions, adjusted R-squared\n\n\n::: {.cell}\n\n:::\n\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(s, data.frame(adjr2, outmat)) %>% \n rownames_to_column(\"model\") %>% \n arrange(desc(adjr2))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n## Revisiting the best model\n- Best model was our rut.6:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.6)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Revisiting (2)\n- Regression slopes say that rut depth increases as log-viscosity\ndecreases, `pct.a.surf` increases and `voids` increases. This more or\nless checks out with out scatterplots against `log.viscosity`. \n- We should check residual plots again, though previous scatterplots say\nit’s unlikely that there will be a problem:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng <- ggplot(rut.6, aes(y = .resid, x = .fitted)) + \ngeom_point()\n```\n:::\n\n\n## Residuals against fitted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-39-1.png){width=960}\n:::\n:::\n\n\n## Plotting residuals against x’s\n- Do our trick again to put them all on one plot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\naugment(rut.6, asphalt) %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\",\n ) %>%\n ggplot(aes(y = .resid, x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g2\n```\n:::\n\n\n## Residuals against the x’s\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng2\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-41-1.png){width=960}\n:::\n:::\n\n\n## Comments\n- None of the plots show any sort of pattern. The points all look\nrandom on each plot.\n- On the plot of fitted values (and on the one of log.viscosity), the\npoints seem to form a “left half” and a “right half” with a gap in the\nmiddle. This is not a concern.\n- One of the pct.a.surf values is low outlier (4), shows up top left of\nthat plot.\n- Only two possible values of run; the points in each group look\nrandomly scattered around 0, with equal spreads.\n- Residuals seem to go above zero further than below, suggesting a\nmild non-normality, but not enough to be a problem.\n\n## Variable-selection strategies\n- Expert knowledge.\n- Backward elimination.\n- All possible regressions.\n- Taking a variety of models to experts and asking their opinion.\n- Use a looser cutoff to eliminate variables in backward elimination (eg.\nonly if P-value greater than 0.20).\n- If goal is prediction, eliminating worthless variables less important.\n- If goal is understanding, want to eliminate worthless variables where\npossible.\n- Results of variable selection not always reproducible, so caution\nadvised.\n\n", + "markdown": "---\ntitle: \"Case study: asphalt\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## The asphalt data\n\n- 31 asphalt pavements prepared under different conditions. How does\n quality of pavement depend on these?\n- Variables:\n - `pct.a.surf` Percentage of asphalt in surface layer\n - `pct.a.base` Percentage of asphalt in base layer\n - `fines` Percentage of fines in surface layer\n - `voids` Percentage of voids in surface layer\n - `rut.depth` Change in rut depth per million vehicle passes\n - `viscosity` Viscosity of asphalt\n - `run` 2 data collection periods: 1 for run 1, 0 for run 2.\n- `rut.depth` response. Depends on other variables, how?\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(leaps)\n```\n:::\n\n\nMake sure to load `MASS` before `tidyverse` (for annoying technical\nreasons).\n\n## Getting set up\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/asphalt.txt\"\nasphalt <- read_delim(my_url, \" \")\n```\n:::\n\n\n- Quantitative variables with one response: multiple regression.\n- Some issues here that don't come up in \"simple\" regression; handle\n as we go. (STAB27/STAC67 ideas.)\n\n## The data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Plotting response \"rut depth\" against everything else\n\nSame idea as for plotting separate predictions on one plot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n pivot_longer(\n -rut.depth,\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = rut.depth)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n\"collect all the x-variables together into one column called x, with\nanother column xname saying which x they were, then plot these x's\nagainst rut.depth, a separate facet for each x-variable.\"\n\nI saved this graph to plot later (on the next page).\n\n## The plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-5-1.png){width=960}\n:::\n:::\n\n\n## Interpreting the plots\n\n- One plot of rut depth against each of the six other variables.\n- Get rough idea of what's going on.\n- Trends mostly weak.\n- `viscosity` has strong but non-linear trend.\n- `run` has effect but variability bigger when run is 1.\n- Weak but downward trend for `voids`.\n- Non-linearity of `rut.depth`-`viscosity` relationship should concern\n us.\n\n## Log of `viscosity`: more nearly linear?\n\n- Take this back to asphalt engineer: suggests log of `viscosity`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(asphalt, aes(y = rut.depth, x = log(viscosity))) +\n geom_point() + geom_smooth(se = F) -> g\n```\n:::\n\n\n(plot overleaf)\n\n## Rut depth against log-viscosity\n\n\n::: {.cell}\n\n:::\n\n\n## Comments and next steps\n\n- Not very linear, but better than before.\n- In multiple regression, hard to guess which x's affect response. So\n typically start by predicting from everything else.\n- Model formula has response on left, squiggle, explanatories on right\n joined by plusses:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 <- lm(rut.depth ~ pct.a.surf + pct.a.base + fines +\n voids + log(viscosity) + run, data = asphalt)\nsummary(rut.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = rut.depth ~ pct.a.surf + pct.a.base + fines + voids + \n log(viscosity) + run, data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-4.1211 -1.9075 -0.7175 1.6382 9.5947 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -12.9937 26.2188 -0.496 0.6247 \npct.a.surf 3.9706 2.4966 1.590 0.1248 \npct.a.base 1.2631 3.9703 0.318 0.7531 \nfines 0.1164 1.0124 0.115 0.9094 \nvoids 0.5893 1.3244 0.445 0.6604 \nlog(viscosity) -3.1515 0.9194 -3.428 0.0022 **\nrun -1.9655 3.6472 -0.539 0.5949 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 3.324 on 24 degrees of freedom\nMultiple R-squared: 0.806,\tAdjusted R-squared: 0.7575 \nF-statistic: 16.62 on 6 and 24 DF, p-value: 1.743e-07\n```\n:::\n:::\n\n\n## Regression output: `summary(rut.1)` or:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(rut.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ntidy(rut.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n## Comments\n\n- R-squared 81%, not so bad.\n\n- P-value in `glance` asserts that something helping to predict\n rut.depth.\n\n- Table of coefficients says `log(viscosity)`.\n\n- But confused by clearly non-significant variables: remove those to\n get clearer picture of what is helpful.\n\n- \n\n ## Before we do anything, look at residual plots:\n\n ``` \n (a) of residuals against fitted values (as usual)\n ```\n\n - \n\n (b) of residuals against each explanatory.\n\n- Problem fixes:\n\n - with (a): fix response variable;\n - with some plots in (b): fix those explanatory variables.\n\n## Plot fitted values against residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-9-1.png){width=960}\n:::\n:::\n\n\n## Normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/unnamed-chunk-1-1.png){width=960}\n:::\n:::\n\n\n## Plotting residuals against $x$ variables\n\n- Problem here is that residuals are in the fitted model, and the\n observed $x$-values are in the original data frame `asphalt`.\n- Package broom contains a function `augment` that combines these two\n together so that they can later be plotted: start with a model\n first, and then augment with a data frame:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 %>% augment(asphalt) -> rut.1a\n```\n:::\n\n\n## What does rut.1a contain?\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnames(rut.1a)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] \"pct.a.surf\" \"pct.a.base\" \"fines\" \"voids\" \"rut.depth\" \n [6] \"viscosity\" \"run\" \".fitted\" \".resid\" \".hat\" \n[11] \".sigma\" \".cooksd\" \".std.resid\"\n```\n:::\n:::\n\n\n- all the stuff in original data frame, plus:\n- quantities from regression (starting with a dot)\n\n## Plotting residuals against $x$-variables\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1a %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = .resid)) +\n geom_point() + facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n## The plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-14-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- There is serious curve in plot of residuals vs. fitted values.\n Suggests a transformation of $y$.\n- The residuals-vs-$x$'s plots don't show any serious trends. Worst\n probably that potential curve against log-viscosity.\n- Also, large positive residual, 10, that shows up on all plots.\n Perhaps transformation of $y$ will help with this too.\n- If residual-fitted plot OK, but some residual-$x$ plots not, try\n transforming those $x$'s, eg. by adding $x^2$ to help with curve.\n\n## Which transformation?\n\n- Best way: consult with person who brought you the data.\n- Can't do that here!\n- No idea what transformation would be good.\n- Let data choose: \"Box-Cox transformation\".\n- Scale is that of \"ladder of powers\": power transformation, but 0 is\n log.\n\n## Running Box-Cox\n\nFrom package `MASS`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(rut.depth ~ pct.a.surf + pct.a.base + fines + voids +\n log(viscosity) + run, data = asphalt)\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-15-1.png){width=960}\n:::\n:::\n\n\n## Comments on Box-Cox plot\n\n- $\\lambda$ represents power to transform $y$ with.\n- Best single choice of transformation parameter $\\lambda$ is peak of\n curve, close to 0.\n- Vertical dotted lines give CI for $\\lambda$, about (−0.05, 0.2).\n- $\\lambda = 0$ means \"log\".\n- Narrowness of confidence interval mean that these not supported by\n data:\n - No transformation ($\\lambda = 1$)\n - Square root ($\\lambda = 0.5$)\n - Reciprocal ($\\lambda = −1$).\n\n## Relationships with explanatories\n\n- As before: plot response (now `log(rut.depth)`) against other\n explanatory variables, all in one shot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(y = log(rut.depth), x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g3\n```\n:::\n\n\n## The new plots\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng3\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-17-1.png){width=960}\n:::\n:::\n\n\n## Modelling with transformed response\n\n- These trends look pretty straight, especially with `log.viscosity`.\n- Values of `log.rut.depth` for each `run` have same spread.\n- Other trends weak, but are straight if they exist.\n- Start modelling from the beginning again.\n- Model `log.rut.depth` in terms of everything else, see what can be\n removed:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.2 <- lm(log(rut.depth) ~ pct.a.surf + pct.a.base +\n fines + voids + log(viscosity) + run, data = asphalt)\n```\n:::\n\n\n- use `tidy` from `broom` to display just the coefficients.\n\n## Output\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nsummary(rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(rut.depth) ~ pct.a.surf + pct.a.base + fines + \n voids + log(viscosity) + run, data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.53072 -0.18563 -0.00003 0.20017 0.55079 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.57299 2.43617 -0.646 0.525 \npct.a.surf 0.58358 0.23198 2.516 0.019 * \npct.a.base -0.10337 0.36891 -0.280 0.782 \nfines 0.09775 0.09407 1.039 0.309 \nvoids 0.19885 0.12306 1.616 0.119 \nlog(viscosity) -0.55769 0.08543 -6.528 9.45e-07 ***\nrun 0.34005 0.33889 1.003 0.326 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.3088 on 24 degrees of freedom\nMultiple R-squared: 0.961,\tAdjusted R-squared: 0.9512 \nF-statistic: 98.47 on 6 and 24 DF, p-value: 1.059e-15\n```\n:::\n:::\n\n\n## Taking out everything non-significant\n\n- Try: remove everything but pct.a.surf and log.viscosity:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.3 <- lm(log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)\nsummary(rut.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.61938 -0.21361 0.06635 0.14932 0.63012 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.90014 1.08059 0.833 0.4119 \npct.a.surf 0.39115 0.21879 1.788 0.0846 . \nlog(viscosity) -0.61856 0.02713 -22.797 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.3208 on 28 degrees of freedom\nMultiple R-squared: 0.9509,\tAdjusted R-squared: 0.9474 \nF-statistic: 270.9 on 2 and 28 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\\normalsize\n\n\\footnotesize\n\n- Check that removing all those variables wasn't too much:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(rut.3, rut.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n- $H_0$ : two models equally good; $H_a$ : bigger model better.\n- Null not rejected here; small model as good as the big one, so\n prefer simpler smaller model `rut.3`.\n\n## Find the largest P-value by eye:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Largest P-value is 0.78 for `pct.a.base`, not significant.\n- So remove this first, re-fit and re-assess.\n- Or, as over.\n\n## Get the computer to find the largest P-value for you\n\n- Output from `tidy` is itself a data frame, thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Largest P-value at the bottom.\n\n## Take out `pct.a.base`\n\n- Copy and paste the `lm` code and remove what you're removing:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- lm(log(rut.depth) ~ pct.a.surf + fines + voids + \n log(viscosity) + run, data = asphalt)\ntidy(rut.4) %>% arrange(p.value) %>% dplyr::select(term, p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n- `fines` is next to go, P-value 0.32.\n\n## \"Update\"\n\nAnother way to do the same thing:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- update(rut.2, . ~ . - pct.a.base)\ntidy(rut.4) %>% arrange(p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Again, `fines` is the one to go. (Output identical as it should be.)\n\n## Take out fines:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.5 <- update(rut.4, . ~ . - fines)\ntidy(rut.5) %>% arrange(p.value) %>% dplyr::select(term, p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nCan't take out intercept, so `run`, with P-value 0.36, goes next.\n\n## Take out run:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.6 <- update(rut.5, . ~ . - run)\ntidy(rut.6) %>% arrange(p.value) %>% dplyr::select(term, p.value)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nAgain, can't take out intercept, so largest P-value is for `voids`,\n0.044. But this is significant, so we shouldn't remove `voids`.\n\n## Comments\n\n- Here we stop: `pct.a.surf`, `voids` and `log.viscosity` would all\n make fit significantly worse if removed. So they stay.\n- Different final result from taking things out one at a time (top),\n than by taking out 4 at once (bottom):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(rut.depth) ~ pct.a.surf + voids + log(viscosity), \n data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.53548 -0.20181 -0.01702 0.16748 0.54707 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.02079 1.36430 -0.748 0.4608 \npct.a.surf 0.55547 0.22044 2.520 0.0180 * \nvoids 0.24479 0.11560 2.118 0.0436 * \nlog(viscosity) -0.64649 0.02879 -22.458 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.3025 on 27 degrees of freedom\nMultiple R-squared: 0.9579,\tAdjusted R-squared: 0.9532 \nF-statistic: 204.6 on 3 and 27 DF, p-value: < 2.2e-16\n```\n:::\n\n```{.r .cell-code}\ncoef(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf voids log(viscosity) \n -1.0207945 0.5554686 0.2447934 -0.6464911 \n```\n:::\n\n```{.r .cell-code}\ncoef(rut.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf log(viscosity) \n 0.9001389 0.3911481 -0.6185628 \n```\n:::\n:::\n\n\n- Point: Can make difference which way we go.\n\n## Comments on variable selection\n\n- Best way to decide which $x$'s belong: expert knowledge: which of\n them should be important.\n- Best automatic method: what we did, \"backward selection\".\n- Do not learn about \"stepwise regression\"! [**eg.\n here**](https://towardsdatascience.com/stopping-stepwise-why-stepwise-selection-is-bad-and-what-you-should-use-instead-90818b3f52df)\n- R has function `step` that does backward selection, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(rut.2, direction = \"backward\", test = \"F\")\n```\n:::\n\n\nGets same answer as we did (by removing least significant x).\n\n- Removing non-significant $x$'s may remove interesting ones whose\n P-values happened not to reach 0.05. Consider using less stringent\n cutoff like 0.20 or even bigger.\n- Can also fit all possible regressions, as over (may need to do\n `install.packages(\"leaps\")` first).\n\n## All possible regressions (output over)\n\nUses package `leaps`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nleaps <- regsubsets(log(rut.depth) ~ pct.a.surf + \n pct.a.base + fines + voids + \n log(viscosity) + run, \n data = asphalt, nbest = 2)\ns <- summary(leaps)\nwith(s, data.frame(rsq, outmat)) -> d\n```\n:::\n\n\n## The output\n\n\n::: {.cell}\n\n:::\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rownames_to_column(\"model\") %>% arrange(desc(rsq))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n\n::: {.cell}\n\n:::\n\n\n## Comments\n\n- Problem: even adding a worthless x increases R-squared. So try for\n line where R-squared stops increasing \"too much\", eg. top line (just\n log.viscosity), first 3-variable line (backwards-elimination model).\n Hard to judge.\n- One solution (STAC67): adjusted R-squared, where adding worthless\n variable makes it go down.\n- `data.frame` rather than `tibble` because there are several columns\n in `outmat`.\n\n## All possible regressions, adjusted R-squared\n\n\n::: {.cell}\n\n:::\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(s, data.frame(adjr2, outmat)) %>% \n rownames_to_column(\"model\") %>% \n arrange(desc(adjr2))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n\n::: {.cell}\n\n:::\n\n\n## Revisiting the best model\n\n- Best model was our rut.6:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.6)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Revisiting (2)\n\n- Regression slopes say that rut depth increases as log-viscosity\n decreases, `pct.a.surf` increases and `voids` increases. This more\n or less checks out with out scatterplots against `log.viscosity`.\n- We should check residual plots again, though previous scatterplots\n say it's unlikely that there will be a problem:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng <- ggplot(rut.6, aes(y = .resid, x = .fitted)) + \ngeom_point()\n```\n:::\n\n\n## Residuals against fitted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-39-1.png){width=960}\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.6, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/unnamed-chunk-2-1.png){width=960}\n:::\n:::\n\n\n## Plotting residuals against x's\n\n- Do our trick again to put them all on one plot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\naugment(rut.6, asphalt) %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\",\n ) %>%\n ggplot(aes(y = .resid, x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g2\n```\n:::\n\n\n## Residuals against the x's\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng2\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-revealjs/asphalt-41-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- None of the plots show any sort of pattern. The points all look\n random on each plot.\n- On the plot of fitted values (and on the one of log.viscosity), the\n points seem to form a \"left half\" and a \"right half\" with a gap in\n the middle. This is not a concern.\n- One of the pct.a.surf values is low outlier (4), shows up top left\n of that plot.\n- Only two possible values of run; the points in each group look\n randomly scattered around 0, with equal spreads.\n- Residuals seem to go above zero further than below, suggesting a\n mild non-normality, but not enough to be a problem.\n\n## Variable-selection strategies\n\n- Expert knowledge.\n- Backward elimination.\n- All possible regressions.\n- Taking a variety of models to experts and asking their opinion.\n- Use a looser cutoff to eliminate variables in backward elimination\n (eg. only if P-value greater than 0.20).\n- If goal is prediction, eliminating worthless variables less\n important.\n- If goal is understanding, want to eliminate worthless variables\n where possible.\n- Results of variable selection not always reproducible, so caution\n advised.\n", "supporting": [ "asphalt_files/figure-revealjs" ], diff --git a/_freeze/asphalt/execute-results/tex.json b/_freeze/asphalt/execute-results/tex.json index 014b9f4..2573462 100644 --- a/_freeze/asphalt/execute-results/tex.json +++ b/_freeze/asphalt/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "25e7e208e7eefbed7de9dbae587937fa", + "hash": "6c8eba076b49aa7fbc7a066250547c9f", "result": { - "markdown": "---\ntitle: \"Case study: asphalt\"\n---\n\n\n\n## The asphalt data\n- 31 asphalt pavements prepared under different conditions. How does\nquality of pavement depend on these?\n- Variables:\n - `pct.a.surf` Percentage of asphalt in surface layer\n - `pct.a.base` Percentage of asphalt in base layer\n - `fines` Percentage of fines in surface layer\n - `voids` Percentage of voids in surface layer\n - `rut.depth` Change in rut depth per million vehicle passes\n - `viscosity` Viscosity of asphalt\n - `run` 2 data collection periods: 1 for run 1, 0 for run 2.\n- `rut.depth` response. Depends on other variables, how?\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(leaps)\n```\n:::\n\n\n\nMake sure to load `MASS` before `tidyverse` (for annoying technical reasons).\n\n## Getting set up \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/asphalt.txt\"\nasphalt <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n- Quantitative variables with one response: multiple regression.\n- Some issues here that don’t come up in “simple” regression; handle as\nwe go. (STAB27/STAC67 ideas.)\n\n## The data (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 31 x 7\n pct.a.surf pct.a.base fines voids rut.depth viscosity run\n \n 1 4.68 4.87 8.4 4.92 6.75 2.8 1\n 2 5.19 4.5 6.5 4.56 13 1.4 1\n 3 4.82 4.73 7.9 5.32 14.8 1.4 1\n 4 4.85 4.76 8.3 4.86 12.6 3.3 1\n 5 4.86 4.95 8.4 3.78 8.25 1.7 1\n 6 5.16 4.45 7.4 4.40 10.7 2.9 1\n 7 4.82 5.05 6.8 4.87 7.28 3.7 1\n 8 4.86 4.7 8.6 4.83 12.7 1.7 1\n 9 4.78 4.84 6.7 4.86 12.6 0.92 1\n10 5.16 4.76 7.7 4.03 20.6 0.68 1\n# i 21 more rows\n```\n:::\n:::\n\n\n\n## Plotting response “rut depth” against everything else\n\nSame idea as for plotting separate predictions on one plot:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n pivot_longer(\n -rut.depth,\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = rut.depth)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n\n“collect all the x-variables together into one column called x, with another\ncolumn xname saying which x they were, then plot these x’s against\nrut.depth, a separate facet for each x-variable.”\n\nI saved this graph to plot later (on the next page).\n\n## The plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-5-1.pdf)\n:::\n:::\n\n\n\n## Interpreting the plots\n- One plot of rut depth against each of the six other variables.\n- Get rough idea of what’s going on.\n- Trends mostly weak.\n- `viscosity` has strong but non-linear trend.\n- `run` has effect but variability bigger when run is 1.\n- Weak but downward trend for `voids`.\n- Non-linearity of `rut.depth`-`viscosity` relationship should concern\nus.\n\n## Log of `viscosity`: more nearly linear?\n\n- Take this back to asphalt engineer: suggests log of `viscosity`:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(asphalt, aes(y = rut.depth, x = log(viscosity))) +\n geom_point() + geom_smooth(se = F)\n```\n:::\n\n\n\n(plot overleaf)\n\n## Rut depth against log-viscosity\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-6-1.pdf)\n:::\n:::\n\n\n\n## Comments and next steps\n- Not very linear, but better than before.\n- In multiple regression, hard to guess which x’s affect response. So\ntypically start by predicting from everything else.\n- Model formula has response on left, squiggle, explanatories on right\njoined by plusses:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 <- lm(rut.depth ~ pct.a.surf + pct.a.base + fines +\n voids + log(viscosity) + run, data = asphalt)\n```\n:::\n\n\n\n## Regression output: `summary(rut.1)` or:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(rut.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.806 0.758 3.32 16.6 0.000000174 6 -77.3 171. 182.\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n\n```{.r .cell-code}\ntidy(rut.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -13.0 26.2 -0.496 0.625 \n2 pct.a.surf 3.97 2.50 1.59 0.125 \n3 pct.a.base 1.26 3.97 0.318 0.753 \n4 fines 0.116 1.01 0.115 0.909 \n5 voids 0.589 1.32 0.445 0.660 \n6 log(viscosity) -3.15 0.919 -3.43 0.00220\n7 run -1.97 3.65 -0.539 0.595 \n```\n:::\n:::\n\n\n\\normalsize\n\n## Comments\n- R-squared 81%, not so bad. \n- P-value in `glance` asserts that something helping to predict\nrut.depth.\n- Table of coefficients says `log(viscosity)`.\n- But confused by clearly non-significant variables: remove those to get\nclearer picture of what is helpful.\n- Before we do anything, look at residual plots:\n - (a) of residuals against fitted values (as usual)\n - (b) of residuals against each explanatory.\n- Problem fixes:\n - with (a): fix response variable; \n - with some plots in (b): fix those explanatory variables.\n\n## Plot fitted values against residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-9-1.pdf)\n:::\n:::\n\n\n\n## Plotting residuals against $x$ variables\n- Problem here is that residuals are in the fitted model, and the\nobserved $x$-values are in the original data frame `asphalt`. \n- Package broom contains a function `augment` that combines these two\ntogether so that they can later be plotted: start with a model first, and then augment with a\ndata frame:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 %>% augment(asphalt) -> rut.1a\n```\n:::\n\n\n\n\n## What does rut.1a contain?\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnames(rut.1a)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] \"pct.a.surf\" \"pct.a.base\" \"fines\" \"voids\" \"rut.depth\" \n [6] \"viscosity\" \"run\" \".fitted\" \".resid\" \".hat\" \n[11] \".sigma\" \".cooksd\" \".std.resid\"\n```\n:::\n:::\n\n\n\n- all the stuff in original data frame, plus:\n- quantities from regression (starting with a dot)\n\n\n## Plotting residuals against $x$-variables \n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1a %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = .resid)) +\n geom_point() + facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n\n## The plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-14-1.pdf)\n:::\n:::\n\n\n\n## Comments\n- There is serious curve in plot of residuals vs. fitted values. Suggests a\ntransformation of $y$. \n- The residuals-vs-$x$’s plots don’t show any serious trends. Worst\nprobably that potential curve against log-viscosity.\n- Also, large positive residual, 10, that shows up on all plots. Perhaps\ntransformation of $y$ will help with this too.\n- If residual-fitted plot OK, but some residual-$x$ plots not, try\ntransforming those $x$’s, eg. by adding $x^2$ to help with curve.\n\n## Which transformation?\n- Best way: consult with person who brought you the data.\n- Can’t do that here!\n- No idea what transformation would be good.\n- Let data choose: “Box-Cox transformation”.\n- Scale is that of “ladder of powers”: power transformation, but 0 is\nlog.\n\n\n## Running Box-Cox\n\nFrom package `MASS`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(rut.depth ~ pct.a.surf + pct.a.base + fines + voids +\n log(viscosity) + run, data = asphalt)\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-15-1.pdf)\n:::\n:::\n\n\n\n## Comments on Box-Cox plot\n- $\\lambda$ represents power to transform $y$ with.\n- Best single choice of transformation parameter $\\lambda$ is peak of curve,\nclose to 0.\n- Vertical dotted lines give CI for $\\lambda$, about (−0.05, 0.2).\n- $\\lambda = 0$ means “log”.\n- Narrowness of confidence interval mean that these not supported by\ndata:\n - No transformation ($\\lambda = 1$)\n - Square root ($\\lambda = 0.5$)\n - Reciprocal ($\\lambda = −1$).\n\n## Relationships with explanatories\n- As before: plot response (now `log(rut.depth)`) against other\nexplanatory variables, all in one shot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(y = log(rut.depth), x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g3\n```\n:::\n\n\n\n## The new plots\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng3\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-17-1.pdf)\n:::\n:::\n\n\n\n## Modelling with transformed response\n- These trends look pretty straight, especially with `log.viscosity`.\n- Values of `log.rut.depth` for each `run` have same spread.\n- Other trends weak, but are straight if they exist.\n- Start modelling from the beginning again.\n- Model `log.rut.depth` in terms of everything else, see what can be\nremoved:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.2 <- lm(log(rut.depth) ~ pct.a.surf + pct.a.base +\n fines + voids + log(viscosity) + run, data = asphalt)\n```\n:::\n\n\n\n- use `tidy` from `broom` to display just the coefficients.\n\n## Output\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.57 2.44 -0.646 0.525 \n2 pct.a.surf 0.584 0.232 2.52 0.0190 \n3 pct.a.base -0.103 0.369 -0.280 0.782 \n4 fines 0.0978 0.0941 1.04 0.309 \n5 voids 0.199 0.123 1.62 0.119 \n6 log(viscosity) -0.558 0.0854 -6.53 0.000000945\n7 run 0.340 0.339 1.00 0.326 \n```\n:::\n:::\n\n\n\n## Taking out everything non-significant\n- Try: remove everything but pct.a.surf and log.viscosity:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.3 <- lm(log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)\n```\n:::\n\n\n\\normalsize\n\n\\footnotesize\n- Check that removing all those variables wasn’t too much:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(rut.3, rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nModel 1: log(rut.depth) ~ pct.a.surf + log(viscosity)\nModel 2: log(rut.depth) ~ pct.a.surf + pct.a.base + fines + voids + log(viscosity) + \n run\n Res.Df RSS Df Sum of Sq F Pr(>F)\n1 28 2.8809 \n2 24 2.2888 4 0.59216 1.5523 0.2191\n```\n:::\n:::\n\n\n\\normalsize\n\n- $H_0$ : two models equally good; $H_a$ : bigger model better.\n- Null not rejected here; small model as good as the big one, so prefer\nsimpler smaller model `rut.3`.\n\n## Find the largest P-value by eye:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.57 2.44 -0.646 0.525 \n2 pct.a.surf 0.584 0.232 2.52 0.0190 \n3 pct.a.base -0.103 0.369 -0.280 0.782 \n4 fines 0.0978 0.0941 1.04 0.309 \n5 voids 0.199 0.123 1.62 0.119 \n6 log(viscosity) -0.558 0.0854 -6.53 0.000000945\n7 run 0.340 0.339 1.00 0.326 \n```\n:::\n:::\n\n\n\n- Largest P-value is 0.78 for `pct.a.base`, not significant.\n- So remove this first, re-fit and re-assess.\n- Or, as over.\n\n## Get the computer to find the largest P-value for you\n\n- Output from `tidy` is itself a data frame, thus:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.558 0.0854 -6.53 0.000000945\n2 pct.a.surf 0.584 0.232 2.52 0.0190 \n3 voids 0.199 0.123 1.62 0.119 \n4 fines 0.0978 0.0941 1.04 0.309 \n5 run 0.340 0.339 1.00 0.326 \n6 (Intercept) -1.57 2.44 -0.646 0.525 \n7 pct.a.base -0.103 0.369 -0.280 0.782 \n```\n:::\n:::\n\n\n\n- Largest P-value at the bottom.\n\n## Take out `pct.a.base`\n\n- Copy and paste the `lm` code and remove what you're removing:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- lm(log(rut.depth) ~ pct.a.surf + fines + voids + \n log(viscosity) + run, data = asphalt)\ntidy(rut.4) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.552 0.0818 -6.75 0.000000448\n2 pct.a.surf 0.593 0.225 2.63 0.0143 \n3 voids 0.200 0.121 1.66 0.109 \n4 (Intercept) -2.08 1.61 -1.29 0.208 \n5 run 0.360 0.325 1.11 0.279 \n6 fines 0.0889 0.0870 1.02 0.316 \n```\n:::\n:::\n\n\n\\normalsize\n\n- `fines` is next to go, P-value 0.32.\n\n## “Update”\n\nAnother way to do the same thing:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- update(rut.2, . ~ . - pct.a.base)\ntidy(rut.4) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.552 0.0818 -6.75 0.000000448\n2 pct.a.surf 0.593 0.225 2.63 0.0143 \n3 voids 0.200 0.121 1.66 0.109 \n4 (Intercept) -2.08 1.61 -1.29 0.208 \n5 run 0.360 0.325 1.11 0.279 \n6 fines 0.0889 0.0870 1.02 0.316 \n```\n:::\n:::\n\n\n\n- Again, `fines` is the one to go. (Output identical as it should be.)\n\n## Take out fines:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.5 <- update(rut.4, . ~ . - fines)\ntidy(rut.5) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.580 0.0772 -7.52 0.0000000559\n2 pct.a.surf 0.548 0.221 2.48 0.0200 \n3 voids 0.232 0.117 1.99 0.0577 \n4 run 0.295 0.319 0.923 0.365 \n5 (Intercept) -1.26 1.39 -0.902 0.375 \n```\n:::\n:::\n\n\n\nCan’t take out intercept, so `run`, with P-value 0.36, goes next.\n\n## Take out run:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.6 <- update(rut.5, . ~ . - run)\ntidy(rut.6) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.646 0.0288 -22.5 5.29e-19\n2 pct.a.surf 0.555 0.220 2.52 1.80e- 2\n3 voids 0.245 0.116 2.12 4.36e- 2\n4 (Intercept) -1.02 1.36 -0.748 4.61e- 1\n```\n:::\n:::\n\n\n\nAgain, can’t take out intercept, so largest P-value is for `voids`, 0.044. But\nthis is significant, so we shouldn’t remove `voids`.\n\n## Comments\n- Here we stop: `pct.a.surf`, `voids` and `log.viscosity` would all\nmake fit significantly worse if removed. So they stay.\n- Different final result from taking things out one at a time (top), than\nby taking out 4 at once (bottom):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoef(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf voids log(viscosity) \n -1.0207945 0.5554686 0.2447934 -0.6464911 \n```\n:::\n\n```{.r .cell-code}\ncoef(rut.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf log(viscosity) \n 0.9001389 0.3911481 -0.6185628 \n```\n:::\n:::\n\n\n\n- Point: Can make difference which way we go.\n\n## Comments on variable selection\n- Best way to decide which $x$’s belong: expert knowledge: which of\nthem should be important.\n- Best automatic method: what we did, “backward selection”.\n- Do not learn about “stepwise regression”! [**eg. here**](https://towardsdatascience.com/stopping-stepwise-why-stepwise-selection-is-bad-and-what-you-should-use-instead-90818b3f52df)\n- R has function `step` that does backward selection, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(rut.2, direction = \"backward\", test = \"F\")\n```\n:::\n\n\n\nGets same answer as we did (by removing least significant x). \n\n- Removing non-significant $x$’s may remove interesting ones whose\nP-values happened not to reach 0.05. Consider using less stringent\ncutoff like 0.20 or even bigger.\n- Can also fit all possible regressions, as over (may need to do\n`install.packages(\"leaps\")` first).\n\n## All possible regressions (output over)\n\nUses package `leaps`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nleaps <- regsubsets(log(rut.depth) ~ pct.a.surf + \n pct.a.base + fines + voids + \n log(viscosity) + run, \n data = asphalt, nbest = 2)\ns <- summary(leaps)\nwith(s, data.frame(rsq, outmat)) -> d\n```\n:::\n\n\n\n## The output\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rownames_to_column(\"model\") %>% arrange(desc(rsq))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n model rsq pct.a.surf pct.a.base fines voids log.viscosity. run\n1 6 ( 1 ) 0.9609642 * * * * * *\n2 5 ( 1 ) 0.9608365 * * * * *\n3 5 ( 2 ) 0.9593265 * * * * * \n4 4 ( 1 ) 0.9591996 * * * *\n5 4 ( 2 ) 0.9589206 * * * * \n6 3 ( 1 ) 0.9578631 * * * \n7 3 ( 2 ) 0.9534561 * * * \n8 2 ( 1 ) 0.9508647 * * \n9 2 ( 2 ) 0.9479541 * * \n10 1 ( 1 ) 0.9452562 * \n11 1 ( 2 ) 0.8624107 *\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n## Comments\n- Problem: even adding a worthless x increases R-squared. So try for\nline where R-squared stops increasing “too much”, eg. top line (just\nlog.viscosity), first 3-variable line (backwards-elimination model).\nHard to judge.\n- One solution (STAC67): adjusted R-squared, where adding worthless\nvariable makes it go down.\n- `data.frame` rather than `tibble` because there are several columns in\n`outmat`. \n\n## All possible regressions, adjusted R-squared\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(s, data.frame(adjr2, outmat)) %>% \n rownames_to_column(\"model\") %>% \n arrange(desc(adjr2))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n model adjr2 pct.a.surf pct.a.base fines voids log.viscosity. run\n1 3 ( 1 ) 0.9531812 * * * \n2 5 ( 1 ) 0.9530038 * * * * *\n3 4 ( 1 ) 0.9529226 * * * *\n4 4 ( 2 ) 0.9526007 * * * * \n5 6 ( 1 ) 0.9512052 * * * * * *\n6 5 ( 2 ) 0.9511918 * * * * * \n7 3 ( 2 ) 0.9482845 * * * \n8 2 ( 1 ) 0.9473550 * * \n9 2 ( 2 ) 0.9442365 * * \n10 1 ( 1 ) 0.9433685 * \n11 1 ( 2 ) 0.8576662 *\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n\n::: {.cell}\n\n:::\n\n\n\n## Revisiting the best model\n- Best model was our rut.6:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.02 1.36 -0.748 4.61e- 1\n2 pct.a.surf 0.555 0.220 2.52 1.80e- 2\n3 voids 0.245 0.116 2.12 4.36e- 2\n4 log(viscosity) -0.646 0.0288 -22.5 5.29e-19\n```\n:::\n:::\n\n\n\n## Revisiting (2)\n- Regression slopes say that rut depth increases as log-viscosity\ndecreases, `pct.a.surf` increases and `voids` increases. This more or\nless checks out with out scatterplots against `log.viscosity`. \n- We should check residual plots again, though previous scatterplots say\nit’s unlikely that there will be a problem:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng <- ggplot(rut.6, aes(y = .resid, x = .fitted)) + \ngeom_point()\n```\n:::\n\n\n\n## Residuals against fitted values\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-39-1.pdf)\n:::\n:::\n\n\n\n## Plotting residuals against x’s\n- Do our trick again to put them all on one plot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\naugment(rut.6, asphalt) %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\",\n ) %>%\n ggplot(aes(y = .resid, x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g2\n```\n:::\n\n\n\n## Residuals against the x’s\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng2\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-41-1.pdf)\n:::\n:::\n\n\n\n## Comments\n- None of the plots show any sort of pattern. The points all look\nrandom on each plot.\n- On the plot of fitted values (and on the one of log.viscosity), the\npoints seem to form a “left half” and a “right half” with a gap in the\nmiddle. This is not a concern.\n- One of the pct.a.surf values is low outlier (4), shows up top left of\nthat plot.\n- Only two possible values of run; the points in each group look\nrandomly scattered around 0, with equal spreads.\n- Residuals seem to go above zero further than below, suggesting a\nmild non-normality, but not enough to be a problem.\n\n## Variable-selection strategies\n- Expert knowledge.\n- Backward elimination.\n- All possible regressions.\n- Taking a variety of models to experts and asking their opinion.\n- Use a looser cutoff to eliminate variables in backward elimination (eg.\nonly if P-value greater than 0.20).\n- If goal is prediction, eliminating worthless variables less important.\n- If goal is understanding, want to eliminate worthless variables where\npossible.\n- Results of variable selection not always reproducible, so caution\nadvised.\n\n", + "markdown": "---\ntitle: \"Case study: asphalt\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## The asphalt data\n\n- 31 asphalt pavements prepared under different conditions. How does\n quality of pavement depend on these?\n- Variables:\n - `pct.a.surf` Percentage of asphalt in surface layer\n - `pct.a.base` Percentage of asphalt in base layer\n - `fines` Percentage of fines in surface layer\n - `voids` Percentage of voids in surface layer\n - `rut.depth` Change in rut depth per million vehicle passes\n - `viscosity` Viscosity of asphalt\n - `run` 2 data collection periods: 1 for run 1, 0 for run 2.\n- `rut.depth` response. Depends on other variables, how?\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(leaps)\n```\n:::\n\n\n\nMake sure to load `MASS` before `tidyverse` (for annoying technical\nreasons).\n\n## Getting set up\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/asphalt.txt\"\nasphalt <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n- Quantitative variables with one response: multiple regression.\n- Some issues here that don't come up in \"simple\" regression; handle\n as we go. (STAB27/STAC67 ideas.)\n\n## The data (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 31 x 7\n pct.a.surf pct.a.base fines voids rut.depth viscosity run\n \n 1 4.68 4.87 8.4 4.92 6.75 2.8 1\n 2 5.19 4.5 6.5 4.56 13 1.4 1\n 3 4.82 4.73 7.9 5.32 14.8 1.4 1\n 4 4.85 4.76 8.3 4.86 12.6 3.3 1\n 5 4.86 4.95 8.4 3.78 8.25 1.7 1\n 6 5.16 4.45 7.4 4.40 10.7 2.9 1\n 7 4.82 5.05 6.8 4.87 7.28 3.7 1\n 8 4.86 4.7 8.6 4.83 12.7 1.7 1\n 9 4.78 4.84 6.7 4.86 12.6 0.92 1\n10 5.16 4.76 7.7 4.03 20.6 0.68 1\n# i 21 more rows\n```\n:::\n:::\n\n\n\n## Plotting response \"rut depth\" against everything else\n\nSame idea as for plotting separate predictions on one plot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n pivot_longer(\n -rut.depth,\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = rut.depth)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n\n\"collect all the x-variables together into one column called x, with\nanother column xname saying which x they were, then plot these x's\nagainst rut.depth, a separate facet for each x-variable.\"\n\nI saved this graph to plot later (on the next page).\n\n## The plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-5-1.pdf)\n:::\n:::\n\n\n\n## Interpreting the plots\n\n- One plot of rut depth against each of the six other variables.\n- Get rough idea of what's going on.\n- Trends mostly weak.\n- `viscosity` has strong but non-linear trend.\n- `run` has effect but variability bigger when run is 1.\n- Weak but downward trend for `voids`.\n- Non-linearity of `rut.depth`-`viscosity` relationship should concern\n us.\n\n## Log of `viscosity`: more nearly linear?\n\n- Take this back to asphalt engineer: suggests log of `viscosity`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(asphalt, aes(y = rut.depth, x = log(viscosity))) +\n geom_point() + geom_smooth(se = F) -> g\n```\n:::\n\n\n\n(plot overleaf)\n\n## Rut depth against log-viscosity\n\n\n\n::: {.cell}\n\n:::\n\n\n\n## Comments and next steps\n\n- Not very linear, but better than before.\n- In multiple regression, hard to guess which x's affect response. So\n typically start by predicting from everything else.\n- Model formula has response on left, squiggle, explanatories on right\n joined by plusses:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 <- lm(rut.depth ~ pct.a.surf + pct.a.base + fines +\n voids + log(viscosity) + run, data = asphalt)\nsummary(rut.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = rut.depth ~ pct.a.surf + pct.a.base + fines + voids + \n log(viscosity) + run, data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-4.1211 -1.9075 -0.7175 1.6382 9.5947 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -12.9937 26.2188 -0.496 0.6247 \npct.a.surf 3.9706 2.4966 1.590 0.1248 \npct.a.base 1.2631 3.9703 0.318 0.7531 \nfines 0.1164 1.0124 0.115 0.9094 \nvoids 0.5893 1.3244 0.445 0.6604 \nlog(viscosity) -3.1515 0.9194 -3.428 0.0022 **\nrun -1.9655 3.6472 -0.539 0.5949 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 3.324 on 24 degrees of freedom\nMultiple R-squared: 0.806,\tAdjusted R-squared: 0.7575 \nF-statistic: 16.62 on 6 and 24 DF, p-value: 1.743e-07\n```\n:::\n:::\n\n\n\n## Regression output: `summary(rut.1)` or:\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(rut.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.806 0.758 3.32 16.6 0.000000174 6 -77.3 171. 182.\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n\n```{.r .cell-code}\ntidy(rut.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -13.0 26.2 -0.496 0.625 \n2 pct.a.surf 3.97 2.50 1.59 0.125 \n3 pct.a.base 1.26 3.97 0.318 0.753 \n4 fines 0.116 1.01 0.115 0.909 \n5 voids 0.589 1.32 0.445 0.660 \n6 log(viscosity) -3.15 0.919 -3.43 0.00220\n7 run -1.97 3.65 -0.539 0.595 \n```\n:::\n:::\n\n\n\n\\normalsize\n\n## Comments\n\n- R-squared 81%, not so bad.\n\n- P-value in `glance` asserts that something helping to predict\n rut.depth.\n\n- Table of coefficients says `log(viscosity)`.\n\n- But confused by clearly non-significant variables: remove those to\n get clearer picture of what is helpful.\n\n- \n\n ## Before we do anything, look at residual plots:\n\n ``` \n (a) of residuals against fitted values (as usual)\n ```\n\n - \n\n (b) of residuals against each explanatory.\n\n- Problem fixes:\n\n - with (a): fix response variable;\n - with some plots in (b): fix those explanatory variables.\n\n## Plot fitted values against residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-9-1.pdf)\n:::\n:::\n\n\n\n## Normal quantile plot of residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/unnamed-chunk-1-1.pdf)\n:::\n:::\n\n\n\n## Plotting residuals against $x$ variables\n\n- Problem here is that residuals are in the fitted model, and the\n observed $x$-values are in the original data frame `asphalt`.\n- Package broom contains a function `augment` that combines these two\n together so that they can later be plotted: start with a model\n first, and then augment with a data frame:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1 %>% augment(asphalt) -> rut.1a\n```\n:::\n\n\n\n## What does rut.1a contain?\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nnames(rut.1a)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] \"pct.a.surf\" \"pct.a.base\" \"fines\" \"voids\" \"rut.depth\" \n [6] \"viscosity\" \"run\" \".fitted\" \".resid\" \".hat\" \n[11] \".sigma\" \".cooksd\" \".std.resid\"\n```\n:::\n:::\n\n\n\n- all the stuff in original data frame, plus:\n- quantities from regression (starting with a dot)\n\n## Plotting residuals against $x$-variables\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.1a %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(x = x, y = .resid)) +\n geom_point() + facet_wrap(~xname, scales = \"free\") -> g\n```\n:::\n\n\n\n## The plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-14-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- There is serious curve in plot of residuals vs. fitted values.\n Suggests a transformation of $y$.\n- The residuals-vs-$x$'s plots don't show any serious trends. Worst\n probably that potential curve against log-viscosity.\n- Also, large positive residual, 10, that shows up on all plots.\n Perhaps transformation of $y$ will help with this too.\n- If residual-fitted plot OK, but some residual-$x$ plots not, try\n transforming those $x$'s, eg. by adding $x^2$ to help with curve.\n\n## Which transformation?\n\n- Best way: consult with person who brought you the data.\n- Can't do that here!\n- No idea what transformation would be good.\n- Let data choose: \"Box-Cox transformation\".\n- Scale is that of \"ladder of powers\": power transformation, but 0 is\n log.\n\n## Running Box-Cox\n\nFrom package `MASS`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(rut.depth ~ pct.a.surf + pct.a.base + fines + voids +\n log(viscosity) + run, data = asphalt)\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-15-1.pdf)\n:::\n:::\n\n\n\n## Comments on Box-Cox plot\n\n- $\\lambda$ represents power to transform $y$ with.\n- Best single choice of transformation parameter $\\lambda$ is peak of\n curve, close to 0.\n- Vertical dotted lines give CI for $\\lambda$, about (−0.05, 0.2).\n- $\\lambda = 0$ means \"log\".\n- Narrowness of confidence interval mean that these not supported by\n data:\n - No transformation ($\\lambda = 1$)\n - Square root ($\\lambda = 0.5$)\n - Reciprocal ($\\lambda = −1$).\n\n## Relationships with explanatories\n\n- As before: plot response (now `log(rut.depth)`) against other\n explanatory variables, all in one shot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nasphalt %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\"\n ) %>%\n ggplot(aes(y = log(rut.depth), x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g3\n```\n:::\n\n\n\n## The new plots\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng3\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-17-1.pdf)\n:::\n:::\n\n\n\n## Modelling with transformed response\n\n- These trends look pretty straight, especially with `log.viscosity`.\n- Values of `log.rut.depth` for each `run` have same spread.\n- Other trends weak, but are straight if they exist.\n- Start modelling from the beginning again.\n- Model `log.rut.depth` in terms of everything else, see what can be\n removed:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.2 <- lm(log(rut.depth) ~ pct.a.surf + pct.a.base +\n fines + voids + log(viscosity) + run, data = asphalt)\n```\n:::\n\n\n\n- use `tidy` from `broom` to display just the coefficients.\n\n## Output\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.57 2.44 -0.646 0.525 \n2 pct.a.surf 0.584 0.232 2.52 0.0190 \n3 pct.a.base -0.103 0.369 -0.280 0.782 \n4 fines 0.0978 0.0941 1.04 0.309 \n5 voids 0.199 0.123 1.62 0.119 \n6 log(viscosity) -0.558 0.0854 -6.53 0.000000945\n7 run 0.340 0.339 1.00 0.326 \n```\n:::\n\n```{.r .cell-code}\nsummary(rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(rut.depth) ~ pct.a.surf + pct.a.base + fines + \n voids + log(viscosity) + run, data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.53072 -0.18563 -0.00003 0.20017 0.55079 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.57299 2.43617 -0.646 0.525 \npct.a.surf 0.58358 0.23198 2.516 0.019 * \npct.a.base -0.10337 0.36891 -0.280 0.782 \nfines 0.09775 0.09407 1.039 0.309 \nvoids 0.19885 0.12306 1.616 0.119 \nlog(viscosity) -0.55769 0.08543 -6.528 9.45e-07 ***\nrun 0.34005 0.33889 1.003 0.326 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.3088 on 24 degrees of freedom\nMultiple R-squared: 0.961,\tAdjusted R-squared: 0.9512 \nF-statistic: 98.47 on 6 and 24 DF, p-value: 1.059e-15\n```\n:::\n:::\n\n\n\n## Taking out everything non-significant\n\n- Try: remove everything but pct.a.surf and log.viscosity:\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.3 <- lm(log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)\nsummary(rut.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.61938 -0.21361 0.06635 0.14932 0.63012 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.90014 1.08059 0.833 0.4119 \npct.a.surf 0.39115 0.21879 1.788 0.0846 . \nlog(viscosity) -0.61856 0.02713 -22.797 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.3208 on 28 degrees of freedom\nMultiple R-squared: 0.9509,\tAdjusted R-squared: 0.9474 \nF-statistic: 270.9 on 2 and 28 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n\\normalsize\n\n\\footnotesize\n\n- Check that removing all those variables wasn't too much:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(rut.3, rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nModel 1: log(rut.depth) ~ pct.a.surf + log(viscosity)\nModel 2: log(rut.depth) ~ pct.a.surf + pct.a.base + fines + voids + log(viscosity) + \n run\n Res.Df RSS Df Sum of Sq F Pr(>F)\n1 28 2.8809 \n2 24 2.2888 4 0.59216 1.5523 0.2191\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- $H_0$ : two models equally good; $H_a$ : bigger model better.\n- Null not rejected here; small model as good as the big one, so\n prefer simpler smaller model `rut.3`.\n\n## Find the largest P-value by eye:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.57 2.44 -0.646 0.525 \n2 pct.a.surf 0.584 0.232 2.52 0.0190 \n3 pct.a.base -0.103 0.369 -0.280 0.782 \n4 fines 0.0978 0.0941 1.04 0.309 \n5 voids 0.199 0.123 1.62 0.119 \n6 log(viscosity) -0.558 0.0854 -6.53 0.000000945\n7 run 0.340 0.339 1.00 0.326 \n```\n:::\n:::\n\n\n\n- Largest P-value is 0.78 for `pct.a.base`, not significant.\n- So remove this first, re-fit and re-assess.\n- Or, as over.\n\n## Get the computer to find the largest P-value for you\n\n- Output from `tidy` is itself a data frame, thus:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.2) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.558 0.0854 -6.53 0.000000945\n2 pct.a.surf 0.584 0.232 2.52 0.0190 \n3 voids 0.199 0.123 1.62 0.119 \n4 fines 0.0978 0.0941 1.04 0.309 \n5 run 0.340 0.339 1.00 0.326 \n6 (Intercept) -1.57 2.44 -0.646 0.525 \n7 pct.a.base -0.103 0.369 -0.280 0.782 \n```\n:::\n:::\n\n\n\n- Largest P-value at the bottom.\n\n## Take out `pct.a.base`\n\n- Copy and paste the `lm` code and remove what you're removing:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- lm(log(rut.depth) ~ pct.a.surf + fines + voids + \n log(viscosity) + run, data = asphalt)\ntidy(rut.4) %>% arrange(p.value) %>% dplyr::select(term, p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n term p.value\n \n1 log(viscosity) 0.000000448\n2 pct.a.surf 0.0143 \n3 voids 0.109 \n4 (Intercept) 0.208 \n5 run 0.279 \n6 fines 0.316 \n```\n:::\n:::\n\n\n\n\\normalsize\n\n- `fines` is next to go, P-value 0.32.\n\n## \"Update\"\n\nAnother way to do the same thing:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.4 <- update(rut.2, . ~ . - pct.a.base)\ntidy(rut.4) %>% arrange(p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 5\n term estimate std.error statistic p.value\n \n1 log(viscosity) -0.552 0.0818 -6.75 0.000000448\n2 pct.a.surf 0.593 0.225 2.63 0.0143 \n3 voids 0.200 0.121 1.66 0.109 \n4 (Intercept) -2.08 1.61 -1.29 0.208 \n5 run 0.360 0.325 1.11 0.279 \n6 fines 0.0889 0.0870 1.02 0.316 \n```\n:::\n:::\n\n\n\n- Again, `fines` is the one to go. (Output identical as it should be.)\n\n## Take out fines:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.5 <- update(rut.4, . ~ . - fines)\ntidy(rut.5) %>% arrange(p.value) %>% dplyr::select(term, p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 2\n term p.value\n \n1 log(viscosity) 0.0000000559\n2 pct.a.surf 0.0200 \n3 voids 0.0577 \n4 run 0.365 \n5 (Intercept) 0.375 \n```\n:::\n:::\n\n\n\nCan't take out intercept, so `run`, with P-value 0.36, goes next.\n\n## Take out run:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrut.6 <- update(rut.5, . ~ . - run)\ntidy(rut.6) %>% arrange(p.value) %>% dplyr::select(term, p.value)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n term p.value\n \n1 log(viscosity) 5.29e-19\n2 pct.a.surf 1.80e- 2\n3 voids 4.36e- 2\n4 (Intercept) 4.61e- 1\n```\n:::\n:::\n\n\n\nAgain, can't take out intercept, so largest P-value is for `voids`,\n0.044. But this is significant, so we shouldn't remove `voids`.\n\n## Comments\n\n- Here we stop: `pct.a.surf`, `voids` and `log.viscosity` would all\n make fit significantly worse if removed. So they stay.\n- Different final result from taking things out one at a time (top),\n than by taking out 4 at once (bottom):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(rut.depth) ~ pct.a.surf + voids + log(viscosity), \n data = asphalt)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.53548 -0.20181 -0.01702 0.16748 0.54707 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.02079 1.36430 -0.748 0.4608 \npct.a.surf 0.55547 0.22044 2.520 0.0180 * \nvoids 0.24479 0.11560 2.118 0.0436 * \nlog(viscosity) -0.64649 0.02879 -22.458 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.3025 on 27 degrees of freedom\nMultiple R-squared: 0.9579,\tAdjusted R-squared: 0.9532 \nF-statistic: 204.6 on 3 and 27 DF, p-value: < 2.2e-16\n```\n:::\n\n```{.r .cell-code}\ncoef(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf voids log(viscosity) \n -1.0207945 0.5554686 0.2447934 -0.6464911 \n```\n:::\n\n```{.r .cell-code}\ncoef(rut.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n (Intercept) pct.a.surf log(viscosity) \n 0.9001389 0.3911481 -0.6185628 \n```\n:::\n:::\n\n\n\n- Point: Can make difference which way we go.\n\n## Comments on variable selection\n\n- Best way to decide which $x$'s belong: expert knowledge: which of\n them should be important.\n- Best automatic method: what we did, \"backward selection\".\n- Do not learn about \"stepwise regression\"! [**eg.\n here**](https://towardsdatascience.com/stopping-stepwise-why-stepwise-selection-is-bad-and-what-you-should-use-instead-90818b3f52df)\n- R has function `step` that does backward selection, like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(rut.2, direction = \"backward\", test = \"F\")\n```\n:::\n\n\n\nGets same answer as we did (by removing least significant x).\n\n- Removing non-significant $x$'s may remove interesting ones whose\n P-values happened not to reach 0.05. Consider using less stringent\n cutoff like 0.20 or even bigger.\n- Can also fit all possible regressions, as over (may need to do\n `install.packages(\"leaps\")` first).\n\n## All possible regressions (output over)\n\nUses package `leaps`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nleaps <- regsubsets(log(rut.depth) ~ pct.a.surf + \n pct.a.base + fines + voids + \n log(viscosity) + run, \n data = asphalt, nbest = 2)\ns <- summary(leaps)\nwith(s, data.frame(rsq, outmat)) -> d\n```\n:::\n\n\n\n## The output\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rownames_to_column(\"model\") %>% arrange(desc(rsq))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n model rsq pct.a.surf pct.a.base fines voids log.viscosity. run\n1 6 ( 1 ) 0.9609642 * * * * * *\n2 5 ( 1 ) 0.9608365 * * * * *\n3 5 ( 2 ) 0.9593265 * * * * * \n4 4 ( 1 ) 0.9591996 * * * *\n5 4 ( 2 ) 0.9589206 * * * * \n6 3 ( 1 ) 0.9578631 * * * \n7 3 ( 2 ) 0.9534561 * * * \n8 2 ( 1 ) 0.9508647 * * \n9 2 ( 2 ) 0.9479541 * * \n10 1 ( 1 ) 0.9452562 * \n11 1 ( 2 ) 0.8624107 *\n```\n:::\n:::\n\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n\n## Comments\n\n- Problem: even adding a worthless x increases R-squared. So try for\n line where R-squared stops increasing \"too much\", eg. top line (just\n log.viscosity), first 3-variable line (backwards-elimination model).\n Hard to judge.\n- One solution (STAC67): adjusted R-squared, where adding worthless\n variable makes it go down.\n- `data.frame` rather than `tibble` because there are several columns\n in `outmat`.\n\n## All possible regressions, adjusted R-squared\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(s, data.frame(adjr2, outmat)) %>% \n rownames_to_column(\"model\") %>% \n arrange(desc(adjr2))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n model adjr2 pct.a.surf pct.a.base fines voids log.viscosity. run\n1 3 ( 1 ) 0.9531812 * * * \n2 5 ( 1 ) 0.9530038 * * * * *\n3 4 ( 1 ) 0.9529226 * * * *\n4 4 ( 2 ) 0.9526007 * * * * \n5 6 ( 1 ) 0.9512052 * * * * * *\n6 5 ( 2 ) 0.9511918 * * * * * \n7 3 ( 2 ) 0.9482845 * * * \n8 2 ( 1 ) 0.9473550 * * \n9 2 ( 2 ) 0.9442365 * * \n10 1 ( 1 ) 0.9433685 * \n11 1 ( 2 ) 0.8576662 *\n```\n:::\n:::\n\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n\n## Revisiting the best model\n\n- Best model was our rut.6:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(rut.6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.02 1.36 -0.748 4.61e- 1\n2 pct.a.surf 0.555 0.220 2.52 1.80e- 2\n3 voids 0.245 0.116 2.12 4.36e- 2\n4 log(viscosity) -0.646 0.0288 -22.5 5.29e-19\n```\n:::\n:::\n\n\n\n## Revisiting (2)\n\n- Regression slopes say that rut depth increases as log-viscosity\n decreases, `pct.a.surf` increases and `voids` increases. This more\n or less checks out with out scatterplots against `log.viscosity`.\n- We should check residual plots again, though previous scatterplots\n say it's unlikely that there will be a problem:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng <- ggplot(rut.6, aes(y = .resid, x = .fitted)) + \ngeom_point()\n```\n:::\n\n\n\n## Residuals against fitted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-39-1.pdf)\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rut.6, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/unnamed-chunk-2-1.pdf)\n:::\n:::\n\n\n\n## Plotting residuals against x's\n\n- Do our trick again to put them all on one plot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\naugment(rut.6, asphalt) %>%\n mutate(log_vis=log(viscosity)) %>% \n pivot_longer(\n c(pct.a.surf:voids, run, log_vis),\n names_to=\"xname\", values_to=\"x\",\n ) %>%\n ggplot(aes(y = .resid, x = x)) + geom_point() +\n facet_wrap(~xname, scales = \"free\") -> g2\n```\n:::\n\n\n\n## Residuals against the x's\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng2\n```\n\n::: {.cell-output-display}\n![](asphalt_files/figure-beamer/asphalt-41-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- None of the plots show any sort of pattern. The points all look\n random on each plot.\n- On the plot of fitted values (and on the one of log.viscosity), the\n points seem to form a \"left half\" and a \"right half\" with a gap in\n the middle. This is not a concern.\n- One of the pct.a.surf values is low outlier (4), shows up top left\n of that plot.\n- Only two possible values of run; the points in each group look\n randomly scattered around 0, with equal spreads.\n- Residuals seem to go above zero further than below, suggesting a\n mild non-normality, but not enough to be a problem.\n\n## Variable-selection strategies\n\n- Expert knowledge.\n- Backward elimination.\n- All possible regressions.\n- Taking a variety of models to experts and asking their opinion.\n- Use a looser cutoff to eliminate variables in backward elimination\n (eg. only if P-value greater than 0.20).\n- If goal is prediction, eliminating worthless variables less\n important.\n- If goal is understanding, want to eliminate worthless variables\n where possible.\n- Results of variable selection not always reproducible, so caution\n advised.\n", "supporting": [ "asphalt_files/figure-beamer" ], diff --git a/_freeze/asphalt/figure-beamer/asphalt-14-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-14-1.pdf index 3baafc2..e1b7f38 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-14-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-14-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/asphalt-15-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-15-1.pdf index ec3331e..38d548a 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-15-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-15-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/asphalt-17-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-17-1.pdf index 926dc04..2ec33c3 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-17-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-17-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/asphalt-39-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-39-1.pdf index 219cf50..500ec33 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-39-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-39-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/asphalt-41-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-41-1.pdf index 9caaef5..0413692 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-41-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-41-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/asphalt-5-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-5-1.pdf index 39a1856..6e7e830 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-5-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-5-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/asphalt-9-1.pdf b/_freeze/asphalt/figure-beamer/asphalt-9-1.pdf index e08a3ed..e225e96 100644 Binary files a/_freeze/asphalt/figure-beamer/asphalt-9-1.pdf and b/_freeze/asphalt/figure-beamer/asphalt-9-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/unnamed-chunk-1-1.pdf b/_freeze/asphalt/figure-beamer/unnamed-chunk-1-1.pdf new file mode 100644 index 0000000..a5f420f Binary files /dev/null and b/_freeze/asphalt/figure-beamer/unnamed-chunk-1-1.pdf differ diff --git a/_freeze/asphalt/figure-beamer/unnamed-chunk-2-1.pdf b/_freeze/asphalt/figure-beamer/unnamed-chunk-2-1.pdf new file mode 100644 index 0000000..9a6ddd5 Binary files /dev/null and b/_freeze/asphalt/figure-beamer/unnamed-chunk-2-1.pdf differ diff --git a/_freeze/asphalt/figure-revealjs/unnamed-chunk-1-1.png b/_freeze/asphalt/figure-revealjs/unnamed-chunk-1-1.png new file mode 100644 index 0000000..cf883ce Binary files /dev/null and b/_freeze/asphalt/figure-revealjs/unnamed-chunk-1-1.png differ diff --git a/_freeze/asphalt/figure-revealjs/unnamed-chunk-2-1.png b/_freeze/asphalt/figure-revealjs/unnamed-chunk-2-1.png new file mode 100644 index 0000000..5dd9912 Binary files /dev/null and b/_freeze/asphalt/figure-revealjs/unnamed-chunk-2-1.png differ diff --git a/_freeze/bootstrap_R/execute-results/html.json b/_freeze/bootstrap_R/execute-results/html.json index 89f90fb..f323c82 100644 --- a/_freeze/bootstrap_R/execute-results/html.json +++ b/_freeze/bootstrap_R/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "b39d3bfbe34481136f1fea8772965337", + "hash": "7c19f4123ee146ced18cb67d9535aa9a", "result": { - "markdown": "---\ntitle: \"Bootstrap for sampling distribution of sample mean\"\n---\n\n\n\n## Assessing assumptions\n\n- Our $t$-tests assume normality of variable being tested\n- but, Central Limit Theorem says that normality matters less if sample is \"large\"\n- in practice \"approximate normality\" is enough, but how do we assess whether what we have is normal enough?\n- so far, use histogram/boxplot and make a call, allowing for sample size.\n\n## What actually has to be normal\n\n- is: **sampling distribution of sample mean**\n- the distribution of sample mean over *all possible samples*\n- but we only have *one* sample!\n- Idea: assume our sample is representative of the population, and draw samples from our sample (!), with replacement.\n- This gives an idea of what different samples from the population might look like.\n- Called *bootstrap*, after expression \"to pull yourself up by your own bootstraps\".\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Blue Jays attendances\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\njays$attendance\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 48414 17264 15086 14433 21397 34743 44794 14184 15606 18581 19217 21519\n[13] 21312 30430 42917 42419 29306 15062 16402 19014 21195 33086 37929 15168\n[25] 17276\n```\n:::\n:::\n\n\n- A bootstrap sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ns <- sample(jays$attendance, replace = TRUE)\ns\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 21195 34743 21312 44794 16402 19014 34743 21195 17264 18581 19014 19217\n[13] 34743 19217 14433 15062 16402 15062 34743 15062 15086 15168 15086 48414\n[25] 30430\n```\n:::\n:::\n\n\n## Getting mean of bootstrap sample\n\n- A bootstrap sample is same size as original, but contains repeated values (eg. 15062) and missing ones (42917).\n- We need the mean of our bootstrap sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(s)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 23055.28\n```\n:::\n:::\n\n\n- This is a little different from the mean of our actual sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(jays$attendance)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 25070.16\n```\n:::\n:::\n\n\n- Want a sense of how the sample mean might vary, if we were able to take repeated samples from our population.\n- Idea: take lots of *bootstrap* samples, and see how *their* sample means vary.\n\n## Setting up bootstrap sampling\n\n- Begin by setting up a dataframe that contains a row for each bootstrap sample. I usually call this column `sim`. Do just 4 to get the idea:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Drawing the bootstrap samples\n\n- Then set up to work one row at a time, and draw a bootstrap sample of the attendances in each row:\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n- Each row of our dataframe contains *all* of a bootstrap sample of 25 observations drawn with replacement from the attendances.\n\n\\normalsize\n\n## Sample means\n\n- Find the mean of each sample:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n- These are (four simulated values of) the bootstrapped sampling distribution of the sample mean.\n\n## Make a histogram of them\n\n- rather pointless here, but to get the idea:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 3) -> g\n```\n:::\n\n\\normalsize\n\n## The (pointless) histogram\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-10-1.png){width=960}\n:::\n:::\n\n\n## Now do again with a decent number of bootstrap samples\n\n- say 1000, and put a decent number of bins on the histogram also:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) -> g\n```\n:::\n\n\\normalsize\n\n## The (better) histogram\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-12-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- This is very close to normal\n- The bootstrap says that the sampling distribution of the sample mean is close to normal, even though the distribution of the data is not\n- A sample size of 25 is big enough to overcome the skewness that we saw\n- This is the Central Limit Theorem in practice\n- It is surprisingly powerful.\n- Thus, the $t$-test is actually perfectly good here.\n\n## Comments on the code 1/2\n\n- You might have been wondering about this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments on the code 2/2\n\n\n- how did we squeeze all 25 sample values into one cell?\n - sample is a so-called \"list-column\" that can contain anything.\n- why did we have to put `list()` around the `sample()`?\n - because `sample` produces a collection of numbers, not just a single one\n - the `list()` signals this: \"make a list-column of samples\".\n \n \n## Two samples\n\n- Assumption: *both* samples are from a normal distribution.\n- In practice, each sample is \"normal enough\" given its sample size, since Central Limit Theorem will help.\n- Use bootstrap on each group independently, as above.\n\n## Kids learning to read\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(kids, aes(x=group, y=score)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-15-1.png){width=960}\n:::\n:::\n\n\n\n## Getting just the control group \n\n- Use `filter` to select rows where something is true:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"c\") -> controls\ncontrols\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Bootstrap these\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(controls$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-17-1.png){width=960}\n:::\n:::\n\n\n## ... and the treatment group:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"t\") -> treats\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(treats$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-19-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- sampling distributions of sample means both look pretty normal\n- as we thought, no problems with our two-sample $t$ at all.\n\n", + "markdown": "---\ntitle: \"Bootstrap for sampling distribution of sample mean\"\n---\n\n\n## Assessing assumptions\n\n- Our $t$-tests assume normality of variable being tested\n- but, Central Limit Theorem says that normality matters less if\n sample is \"large\"\n- in practice \"approximate normality\" is enough, but how do we assess\n whether what we have is normal enough?\n- so far, use histogram/boxplot and make a call, allowing for sample\n size.\n\n## What actually has to be normal\n\n- is: **sampling distribution of sample mean**\n- the distribution of sample mean over *all possible samples*\n- but we only have *one* sample!\n- Idea: assume our sample is representative of the population, and\n draw samples from our sample (!), with replacement.\n- This gives an idea of what different samples from the population\n might look like.\n- Called *bootstrap*, after expression \"to pull yourself up by your\n own bootstraps\".\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## Blue Jays attendances\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\njays$attendance\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 48414 17264 15086 14433 21397 34743 44794 14184 15606 18581 19217 21519\n[13] 21312 30430 42917 42419 29306 15062 16402 19014 21195 33086 37929 15168\n[25] 17276\n```\n:::\n:::\n\n\n- A bootstrap sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ns <- sample(jays$attendance, replace = TRUE)\ns\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 21195 34743 21312 44794 16402 19014 34743 21195 17264 18581 19014 19217\n[13] 34743 19217 14433 15062 16402 15062 34743 15062 15086 15168 15086 48414\n[25] 30430\n```\n:::\n:::\n\n\n- It is easier to see what is happening if we sort both the actual\n attendances and the bootstrap sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsort(jays$attendance)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 14184 14433 15062 15086 15168 15606 16402 17264 17276 18581 19014 19217\n[13] 21195 21312 21397 21519 29306 30430 33086 34743 37929 42419 42917 44794\n[25] 48414\n```\n:::\n\n```{.r .cell-code}\nsort(s)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 14433 15062 15062 15062 15086 15086 15168 16402 16402 17264 18581 19014\n[13] 19014 19217 19217 21195 21195 21312 30430 34743 34743 34743 34743 44794\n[25] 48414\n```\n:::\n:::\n\n\n## Getting mean of bootstrap sample\n\n- A bootstrap sample is same size as original, but contains repeated\n values (eg. 15062) and missing ones (42917).\n- We need the mean of our bootstrap sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(s)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 23055.28\n```\n:::\n:::\n\n\n- This is a little different from the mean of our actual sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(jays$attendance)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 25070.16\n```\n:::\n:::\n\n\n- Want a sense of how the sample mean might vary, if we were able to\n take repeated samples from our population.\n- Idea: take lots of *bootstrap* samples, and see how *their* sample\n means vary.\n\n## Setting up bootstrap sampling\n\n- Begin by setting up a dataframe that contains a row for each\n bootstrap sample. I usually call this column `sim`. Do just 4 to get\n the idea:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Drawing the bootstrap samples\n\n- Then set up to work one row at a time, and draw a bootstrap sample\n of the attendances in each row:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Each row of our dataframe contains *all* of a bootstrap sample of 25\n observations drawn with replacement from the attendances.\n\n## Sample means\n\n- Find the mean of each sample:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- These are (four simulated values of) the bootstrapped sampling\n distribution of the sample mean.\n\n## Make a histogram of them\n\n- rather pointless here, but to get the idea:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 3) -> g\n```\n:::\n\n\n## The (pointless) histogram\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-10-1.png){width=960}\n:::\n:::\n\n\n## Now do again with a decent number of bootstrap samples\n\n- say 1000, and put a decent number of bins on the histogram also:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) -> g\n```\n:::\n\n\n## The (better) histogram\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-12-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- This is very close to normal\n- The bootstrap says that the sampling distribution of the sample mean\n is close to normal, even though the distribution of the data is not\n- A sample size of 25 is big enough to overcome the skewness that we\n saw\n- This is the Central Limit Theorem in practice\n- It is surprisingly powerful.\n- Thus, the $t$-test is actually perfectly good here.\n\n## Comments on the code 1/2\n\n- You might have been wondering about this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments on the code 2/2\n\n- how did we squeeze all 25 sample values into one cell?\n - sample is a so-called \"list-column\" that can contain anything.\n- why did we have to put `list()` around the `sample()`?\n - because `sample` produces a collection of numbers, not just a\n single one\n - the `list()` signals this: \"make a list-column of samples\".\n\n## Two samples\n\n- Assumption: *both* samples are from a normal distribution.\n- In this case, each sample should be \"normal enough\" given its sample\n size, since Central Limit Theorem will help.\n- Use bootstrap on each group independently, as above.\n\n## Kids learning to read\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(kids, aes(x=group, y=score)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-15-1.png){width=960}\n:::\n:::\n\n\n## Getting just the control group\n\n- Use `filter` to select rows where something is true:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"c\") -> controls\ncontrols\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Bootstrap these\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(controls$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-17-1.png){width=960}\n:::\n:::\n\n\n## ... and the treatment group:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"t\") -> treats\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(treats$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-revealjs/bootstrap-R-19-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- sampling distributions of sample means both look pretty normal\n- as we thought, no problems with our two-sample $t$ at all.\n", "supporting": [ "bootstrap_R_files/figure-revealjs" ], diff --git a/_freeze/bootstrap_R/execute-results/tex.json b/_freeze/bootstrap_R/execute-results/tex.json index efd6dd4..d9ae06d 100644 --- a/_freeze/bootstrap_R/execute-results/tex.json +++ b/_freeze/bootstrap_R/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "b39d3bfbe34481136f1fea8772965337", + "hash": "7c19f4123ee146ced18cb67d9535aa9a", "result": { - "markdown": "---\ntitle: \"Bootstrap for sampling distribution of sample mean\"\n---\n\n\n\n\n## Assessing assumptions\n\n- Our $t$-tests assume normality of variable being tested\n- but, Central Limit Theorem says that normality matters less if sample is \"large\"\n- in practice \"approximate normality\" is enough, but how do we assess whether what we have is normal enough?\n- so far, use histogram/boxplot and make a call, allowing for sample size.\n\n## What actually has to be normal\n\n- is: **sampling distribution of sample mean**\n- the distribution of sample mean over *all possible samples*\n- but we only have *one* sample!\n- Idea: assume our sample is representative of the population, and draw samples from our sample (!), with replacement.\n- This gives an idea of what different samples from the population might look like.\n- Called *bootstrap*, after expression \"to pull yourself up by your own bootstraps\".\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n\n## Blue Jays attendances\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\njays$attendance\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 48414 17264 15086 14433 21397 34743 44794 14184 15606 18581 19217 21519\n[13] 21312 30430 42917 42419 29306 15062 16402 19014 21195 33086 37929 15168\n[25] 17276\n```\n:::\n:::\n\n\n\n- A bootstrap sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ns <- sample(jays$attendance, replace = TRUE)\ns\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 21195 34743 21312 44794 16402 19014 34743 21195 17264 18581 19014 19217\n[13] 34743 19217 14433 15062 16402 15062 34743 15062 15086 15168 15086 48414\n[25] 30430\n```\n:::\n:::\n\n\n\n## Getting mean of bootstrap sample\n\n- A bootstrap sample is same size as original, but contains repeated values (eg. 15062) and missing ones (42917).\n- We need the mean of our bootstrap sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(s)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 23055.28\n```\n:::\n:::\n\n\n\n- This is a little different from the mean of our actual sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(jays$attendance)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 25070.16\n```\n:::\n:::\n\n\n\n- Want a sense of how the sample mean might vary, if we were able to take repeated samples from our population.\n- Idea: take lots of *bootstrap* samples, and see how *their* sample means vary.\n\n## Setting up bootstrap sampling\n\n- Begin by setting up a dataframe that contains a row for each bootstrap sample. I usually call this column `sim`. Do just 4 to get the idea:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 1\n sim\n \n1 1\n2 2\n3 3\n4 4\n```\n:::\n:::\n\n\n\n## Drawing the bootstrap samples\n\n- Then set up to work one row at a time, and draw a bootstrap sample of the attendances in each row:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n# Rowwise: \n sim sample \n \n1 1 \n2 2 \n3 3 \n4 4 \n```\n:::\n:::\n\n\n\\normalsize\n\n- Each row of our dataframe contains *all* of a bootstrap sample of 25 observations drawn with replacement from the attendances.\n\n\\normalsize\n\n## Sample means\n\n- Find the mean of each sample:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 3\n# Rowwise: \n sim sample my_mean\n \n1 1 28472.\n2 2 28648.\n3 3 23329.\n4 4 24808.\n```\n:::\n:::\n\n\n\\normalsize\n\n- These are (four simulated values of) the bootstrapped sampling distribution of the sample mean.\n\n## Make a histogram of them\n\n- rather pointless here, but to get the idea:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 3) -> g\n```\n:::\n\n\n\\normalsize\n\n## The (pointless) histogram\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-10-1.pdf)\n:::\n:::\n\n\n\n## Now do again with a decent number of bootstrap samples\n\n- say 1000, and put a decent number of bins on the histogram also:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) -> g\n```\n:::\n\n\n\\normalsize\n\n## The (better) histogram\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-12-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- This is very close to normal\n- The bootstrap says that the sampling distribution of the sample mean is close to normal, even though the distribution of the data is not\n- A sample size of 25 is big enough to overcome the skewness that we saw\n- This is the Central Limit Theorem in practice\n- It is surprisingly powerful.\n- Thus, the $t$-test is actually perfectly good here.\n\n## Comments on the code 1/2\n\n- You might have been wondering about this:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n# Rowwise: \n sim sample \n \n1 1 \n2 2 \n3 3 \n4 4 \n```\n:::\n:::\n\n\n\n## Comments on the code 2/2\n\n\n- how did we squeeze all 25 sample values into one cell?\n - sample is a so-called \"list-column\" that can contain anything.\n- why did we have to put `list()` around the `sample()`?\n - because `sample` produces a collection of numbers, not just a single one\n - the `list()` signals this: \"make a list-column of samples\".\n \n \n## Two samples\n\n- Assumption: *both* samples are from a normal distribution.\n- In practice, each sample is \"normal enough\" given its sample size, since Central Limit Theorem will help.\n- Use bootstrap on each group independently, as above.\n\n## Kids learning to read\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(kids, aes(x=group, y=score)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-15-1.pdf)\n:::\n:::\n\n\n\n\n## Getting just the control group \n\n- Use `filter` to select rows where something is true:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"c\") -> controls\ncontrols\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 23 x 2\n group score\n \n 1 c 42\n 2 c 33\n 3 c 46\n 4 c 37\n 5 c 43\n 6 c 41\n 7 c 10\n 8 c 42\n 9 c 55\n10 c 19\n# i 13 more rows\n```\n:::\n:::\n\n\n\n## Bootstrap these\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(controls$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-17-1.pdf)\n:::\n:::\n\n\n\n## ... and the treatment group:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"t\") -> treats\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(treats$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-19-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- sampling distributions of sample means both look pretty normal\n- as we thought, no problems with our two-sample $t$ at all.\n\n", + "markdown": "---\ntitle: \"Bootstrap for sampling distribution of sample mean\"\n---\n\n\n\n## Assessing assumptions\n\n- Our $t$-tests assume normality of variable being tested\n- but, Central Limit Theorem says that normality matters less if\n sample is \"large\"\n- in practice \"approximate normality\" is enough, but how do we assess\n whether what we have is normal enough?\n- so far, use histogram/boxplot and make a call, allowing for sample\n size.\n\n## What actually has to be normal\n\n- is: **sampling distribution of sample mean**\n- the distribution of sample mean over *all possible samples*\n- but we only have *one* sample!\n- Idea: assume our sample is representative of the population, and\n draw samples from our sample (!), with replacement.\n- This gives an idea of what different samples from the population\n might look like.\n- Called *bootstrap*, after expression \"to pull yourself up by your\n own bootstraps\".\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Blue Jays attendances\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\njays$attendance\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 48414 17264 15086 14433 21397 34743 44794 14184 15606 18581 19217 21519\n[13] 21312 30430 42917 42419 29306 15062 16402 19014 21195 33086 37929 15168\n[25] 17276\n```\n:::\n:::\n\n\n\n- A bootstrap sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ns <- sample(jays$attendance, replace = TRUE)\ns\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 21195 34743 21312 44794 16402 19014 34743 21195 17264 18581 19014 19217\n[13] 34743 19217 14433 15062 16402 15062 34743 15062 15086 15168 15086 48414\n[25] 30430\n```\n:::\n:::\n\n\n\n- It is easier to see what is happening if we sort both the actual\n attendances and the bootstrap sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsort(jays$attendance)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 14184 14433 15062 15086 15168 15606 16402 17264 17276 18581 19014 19217\n[13] 21195 21312 21397 21519 29306 30430 33086 34743 37929 42419 42917 44794\n[25] 48414\n```\n:::\n\n```{.r .cell-code}\nsort(s)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 14433 15062 15062 15062 15086 15086 15168 16402 16402 17264 18581 19014\n[13] 19014 19217 19217 21195 21195 21312 30430 34743 34743 34743 34743 44794\n[25] 48414\n```\n:::\n:::\n\n\n\n## Getting mean of bootstrap sample\n\n- A bootstrap sample is same size as original, but contains repeated\n values (eg. 15062) and missing ones (42917).\n- We need the mean of our bootstrap sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(s)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 23055.28\n```\n:::\n:::\n\n\n\n- This is a little different from the mean of our actual sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmean(jays$attendance)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 25070.16\n```\n:::\n:::\n\n\n\n- Want a sense of how the sample mean might vary, if we were able to\n take repeated samples from our population.\n- Idea: take lots of *bootstrap* samples, and see how *their* sample\n means vary.\n\n## Setting up bootstrap sampling\n\n- Begin by setting up a dataframe that contains a row for each\n bootstrap sample. I usually call this column `sim`. Do just 4 to get\n the idea:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 1\n sim\n \n1 1\n2 2\n3 3\n4 4\n```\n:::\n:::\n\n\n\n## Drawing the bootstrap samples\n\n- Then set up to work one row at a time, and draw a bootstrap sample\n of the attendances in each row:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n# Rowwise: \n sim sample \n \n1 1 \n2 2 \n3 3 \n4 4 \n```\n:::\n:::\n\n\n\n- Each row of our dataframe contains *all* of a bootstrap sample of 25\n observations drawn with replacement from the attendances.\n\n## Sample means\n\n- Find the mean of each sample:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 3\n# Rowwise: \n sim sample my_mean\n \n1 1 28472.\n2 2 28648.\n3 3 23329.\n4 4 24808.\n```\n:::\n:::\n\n\n\n- These are (four simulated values of) the bootstrapped sampling\n distribution of the sample mean.\n\n## Make a histogram of them\n\n- rather pointless here, but to get the idea:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 3) -> g\n```\n:::\n\n\n\n## The (pointless) histogram\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-10-1.pdf)\n:::\n:::\n\n\n\n## Now do again with a decent number of bootstrap samples\n\n- say 1000, and put a decent number of bins on the histogram also:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) -> g\n```\n:::\n\n\n\n## The (better) histogram\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-12-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- This is very close to normal\n- The bootstrap says that the sampling distribution of the sample mean\n is close to normal, even though the distribution of the data is not\n- A sample size of 25 is big enough to overcome the skewness that we\n saw\n- This is the Central Limit Theorem in practice\n- It is surprisingly powerful.\n- Thus, the $t$-test is actually perfectly good here.\n\n## Comments on the code 1/2\n\n- You might have been wondering about this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:4) %>% \n rowwise() %>% \n mutate(sample = list(sample(jays$attendance, replace = TRUE)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n# Rowwise: \n sim sample \n \n1 1 \n2 2 \n3 3 \n4 4 \n```\n:::\n:::\n\n\n\n## Comments on the code 2/2\n\n- how did we squeeze all 25 sample values into one cell?\n - sample is a so-called \"list-column\" that can contain anything.\n- why did we have to put `list()` around the `sample()`?\n - because `sample` produces a collection of numbers, not just a\n single one\n - the `list()` signals this: \"make a list-column of samples\".\n\n## Two samples\n\n- Assumption: *both* samples are from a normal distribution.\n- In this case, each sample should be \"normal enough\" given its sample\n size, since Central Limit Theorem will help.\n- Use bootstrap on each group independently, as above.\n\n## Kids learning to read\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 44 x 2\n group score\n \n 1 t 24\n 2 t 61\n 3 t 59\n 4 t 46\n 5 t 43\n 6 t 44\n 7 t 52\n 8 t 43\n 9 t 58\n10 t 67\n# i 34 more rows\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(kids, aes(x=group, y=score)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-15-1.pdf)\n:::\n:::\n\n\n\n## Getting just the control group\n\n- Use `filter` to select rows where something is true:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"c\") -> controls\ncontrols\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 23 x 2\n group score\n \n 1 c 42\n 2 c 33\n 3 c 46\n 4 c 37\n 5 c 43\n 6 c 41\n 7 c 10\n 8 c 42\n 9 c 55\n10 c 19\n# i 13 more rows\n```\n:::\n:::\n\n\n\n## Bootstrap these\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(controls$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-17-1.pdf)\n:::\n:::\n\n\n\n## ... and the treatment group:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% filter(group==\"t\") -> treats\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(sample = list(sample(treats$score, replace = TRUE))) %>% \n mutate(my_mean = mean(sample)) %>% \n ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) \n```\n\n::: {.cell-output-display}\n![](bootstrap_R_files/figure-beamer/bootstrap-R-19-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- sampling distributions of sample means both look pretty normal\n- as we thought, no problems with our two-sample $t$ at all.\n", "supporting": [ "bootstrap_R_files/figure-beamer" ], diff --git a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-10-1.pdf b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-10-1.pdf index 4ad461b..2f877c4 100644 Binary files a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-10-1.pdf and b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-10-1.pdf differ diff --git a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-12-1.pdf b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-12-1.pdf index 1782906..8b5adc1 100644 Binary files a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-12-1.pdf and b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-12-1.pdf differ diff --git a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-15-1.pdf b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-15-1.pdf index 7cd2a7c..c0a5e5e 100644 Binary files a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-15-1.pdf and b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-15-1.pdf differ diff --git a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-17-1.pdf b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-17-1.pdf index 3bc795e..db21cdd 100644 Binary files a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-17-1.pdf and b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-17-1.pdf differ diff --git a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-19-1.pdf b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-19-1.pdf index 4de1a18..f4ae3fd 100644 Binary files a/_freeze/bootstrap_R/figure-beamer/bootstrap-R-19-1.pdf and b/_freeze/bootstrap_R/figure-beamer/bootstrap-R-19-1.pdf differ diff --git a/_freeze/choosing/execute-results/tex.json b/_freeze/choosing/execute-results/tex.json index b6df1ec..d682186 100644 --- a/_freeze/choosing/execute-results/tex.json +++ b/_freeze/choosing/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "1a27c83e2f5ea4aee31a32c65b194163", + "hash": "8a1b88b16954fb13b78c31ee298ba634", "result": { - "markdown": "---\ntitle: \"Choosing things in dataframes\"\n---\n\n\n\n## Packages\n\nThe usual:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n\n## Doing things with data frames\nLet’s go back to our Australian athletes: \n\n\n\n::: {.cell}\n\n:::\n\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 4.56 13.3 42.2 13.6 20 19.2 49 11.3 53.1\n 2 female Netba~ 4.15 6 38 12.7 59 21.2 110. 25.3 47.1\n 3 female Netba~ 4.16 7.6 37.5 12.3 22 21.4 89 19.4 53.4\n 4 female Netba~ 4.32 6.4 37.7 12.3 30 21.0 98.3 19.6 48.8\n 5 female Netba~ 4.06 5.8 38.7 12.8 78 21.8 122. 23.1 56.0\n 6 female Netba~ 4.12 6.1 36.6 11.8 21 21.4 90.4 16.9 56.4\n 7 female Netba~ 4.17 5 37.4 12.7 109 21.5 107. 21.3 53.1\n 8 female Netba~ 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4\n 9 female Netba~ 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0\n10 female Netba~ 4.44 9.7 41.4 14.1 64 22.8 126. 25.0 51.6\n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n## Choosing a column\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 1\n Sport \n \n 1 Netball\n 2 Netball\n 3 Netball\n 4 Netball\n 5 Netball\n 6 Netball\n 7 Netball\n 8 Netball\n 9 Netball\n10 Netball\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing several columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport, Hg, BMI)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sport Hg BMI\n \n 1 Netball 13.6 19.2\n 2 Netball 12.7 21.2\n 3 Netball 12.3 21.4\n 4 Netball 12.3 21.0\n 5 Netball 12.8 21.8\n 6 Netball 11.8 21.4\n 7 Netball 12.7 21.5\n 8 Netball 12.4 24.4\n 9 Netball 12.4 22.6\n10 Netball 14.1 22.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing consecutive columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sex:WCC)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sex Sport RCC WCC\n \n 1 female Netball 4.56 13.3\n 2 female Netball 4.15 6 \n 3 female Netball 4.16 7.6\n 4 female Netball 4.32 6.4\n 5 female Netball 4.06 5.8\n 6 female Netball 4.12 6.1\n 7 female Netball 4.17 5 \n 8 female Netball 3.8 6.6\n 9 female Netball 3.96 5.5\n10 female Netball 4.44 9.7\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing all-but some columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(-(RCC:LBM))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sex Sport Ht Wt\n \n 1 female Netball 177. 59.9\n 2 female Netball 173. 63 \n 3 female Netball 176 66.3\n 4 female Netball 170. 60.7\n 5 female Netball 183 72.9\n 6 female Netball 178. 67.9\n 7 female Netball 177. 67.5\n 8 female Netball 174. 74.1\n 9 female Netball 174. 68.2\n10 female Netball 174. 68.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Select-helpers\nOther ways to select columns: those whose name:\n\n- `starts_with` something\n- `ends_with` something\n- `contains` something\n- `matches` a “regular expression”\n- `everything()` select all the columns\n\n## Columns whose names begin with S \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(starts_with(\"S\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sex Sport SSF\n \n 1 female Netball 49 \n 2 female Netball 110. \n 3 female Netball 89 \n 4 female Netball 98.3\n 5 female Netball 122. \n 6 female Netball 90.4\n 7 female Netball 107. \n 8 female Netball 157. \n 9 female Netball 101. \n10 female Netball 126. \n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Columns whose names end with C\n\neither uppercase or lowercase:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"c\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n RCC WCC Hc\n \n 1 4.56 13.3 42.2\n 2 4.15 6 38 \n 3 4.16 7.6 37.5\n 4 4.32 6.4 37.7\n 5 4.06 5.8 38.7\n 6 4.12 6.1 36.6\n 7 4.17 5 37.4\n 8 3.8 6.6 36.5\n 9 3.96 5.5 36.3\n10 4.44 9.7 41.4\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Case-sensitive\n\nThis works with any of the select-helpers:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"C\", ignore.case=FALSE))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n RCC WCC\n \n 1 4.56 13.3\n 2 4.15 6 \n 3 4.16 7.6\n 4 4.32 6.4\n 5 4.06 5.8\n 6 4.12 6.1\n 7 4.17 5 \n 8 3.8 6.6\n 9 3.96 5.5\n10 4.44 9.7\n# i 192 more rows\n```\n:::\n:::\n\n\n\n\n## Column names containing letter R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(contains(\"r\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sport RCC Ferr\n \n 1 Netball 4.56 20\n 2 Netball 4.15 59\n 3 Netball 4.16 22\n 4 Netball 4.32 30\n 5 Netball 4.06 78\n 6 Netball 4.12 21\n 7 Netball 4.17 109\n 8 Netball 3.8 102\n 9 Netball 3.96 71\n10 Netball 4.44 64\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Exactly two characters, ending with T\n\nIn regular expression terms, this is `^.t$`:\n\n- `^` means “start of text”\n- `.` means “exactly one character, but could be anything”\n- `$` means “end of text”.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(matches(\"^.t$\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n Ht Wt\n \n 1 177. 59.9\n 2 173. 63 \n 3 176 66.3\n 4 170. 60.7\n 5 183 72.9\n 6 178. 67.9\n 7 177. 67.5\n 8 174. 74.1\n 9 174. 68.2\n10 174. 68.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing columns by property\n\n- Use `where` as with summarizing several columns\n- eg, to choose text columns:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(where(is.character))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n Sex Sport \n \n 1 female Netball\n 2 female Netball\n 3 female Netball\n 4 female Netball\n 5 female Netball\n 6 female Netball\n 7 female Netball\n 8 female Netball\n 9 female Netball\n10 female Netball\n# i 192 more rows\n```\n:::\n:::\n\n\n\n\n## Choosing rows by number \n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice(16:25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 4.25 10.7 39.5 13.2 127 24.5 157. 26.5 54.5\n 2 female Netba~ 4.46 10.9 39.7 13.7 102 24.0 116. 23.0 57.2\n 3 female Netba~ 4.4 9.3 40.4 13.6 86 26.2 182. 30.1 54.4\n 4 female Netba~ 4.83 8.4 41.8 13.4 40 20.0 71.6 13.9 57.6\n 5 female Netba~ 4.23 6.9 38.3 12.6 50 25.7 144. 26.6 61.5\n 6 female Netba~ 4.24 8.4 37.6 12.5 58 25.6 201. 35.5 53.5\n 7 female Netba~ 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1\n 8 female Netba~ 4.03 8.5 37.7 13 51 23.4 104. 19.6 55.4\n 9 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3\n10 female BBall 4.41 8.3 38.2 12.7 68 20.7 103. 21.3 58.6\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n## Non-consecutive rows \n\n\\tiny\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice(10, 13, 17, 42)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Netball 4.44 9.7 41.4 14.1 64 22.8 126. 25.0 51.6\n2 female Netball 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3\n3 female Netball 4.46 10.9 39.7 13.7 102 24.0 116. 23.0 57.2\n4 female Row 4.37 8.1 41.8 14.3 53 23.5 98 21.8 63.0\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n\\normalsize\n\n## A random sample of rows\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice_sample(n=8)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Row 4.87 6.4 44.8 15 64 20.2 99.8 20.1 52.7\n2 male Tennis 5.66 8.3 50.2 17.7 38 23.8 56.5 10.0 72 \n3 male T400m 4.55 5.55 42.6 14.4 106 21.2 34.1 6.06 57 \n4 female BBall 4.35 7.8 41.4 14.1 30 22.0 118. 23.3 48.3\n5 male Row 5.22 6 46.6 15.7 72 25.1 43.1 7.49 83 \n6 male WPolo 4.63 14.3 44.8 15 133 25.4 49.5 8.97 79 \n7 male WPolo 4.91 10.2 45 15.2 234 23.7 56.5 10.1 68 \n8 male Row 5.22 8.4 47.5 16.2 89 25.3 44.5 9.36 79 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n## Rows for which something is true\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n 2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n 3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n 4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n 5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n 6 female Tennis 5.16 7.2 44.3 14.5 88 18.3 61.9 12.9 48.8\n 7 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9\n 8 male Tennis 5.66 8.3 50.2 17.7 38 23.8 56.5 10.0 72 \n 9 male Tennis 5.03 6.4 42.7 14.3 122 22.0 47.6 8.51 68 \n10 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 \n11 male Tennis 5.38 6.3 46 15.7 32 21.1 34.9 6.26 72 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n## More complicated selections\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\", RCC < 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n6 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9\n7 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Another way to do \"and\"\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\") %>% \n filter(RCC < 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n6 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9\n7 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n\n## Either/Or\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\" | RCC > 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 66 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Row 5.02 6.4 44.8 15.2 48 19.8 91 19.2 53.6\n 2 female T400m 5.31 9.5 47.1 15.9 29 21.4 57.9 11.1 57.5\n 3 female Field 5.33 9.3 47 15 62 25.3 103. 19.5 59.9\n 4 female TSprnt 5.16 8.2 45.3 14.7 34 20.3 46.1 10.2 51.5\n 5 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n 6 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n 7 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n 8 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n 9 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n10 female Tennis 5.16 7.2 44.3 14.5 88 18.3 61.9 12.9 48.8\n# i 56 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Sorting into order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4\n 2 female Netba~ 3.9 6.3 35.9 12.1 78 20.1 70 15.0 57.3\n 3 female T400m 3.9 6 38.9 13.5 16 19.4 48.4 10.5 53.7\n 4 female Row 3.91 7.3 37.6 12.9 43 22.3 126. 25.2 54.8\n 5 female Netba~ 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1\n 6 female Row 3.95 3.3 36.9 12.5 40 24.5 74.9 16.4 63.0\n 7 female Netba~ 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0\n 8 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3\n 9 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n10 female Netba~ 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3\n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Breaking ties by another variable\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC, BMI)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4\n 2 female T400m 3.9 6 38.9 13.5 16 19.4 48.4 10.5 53.7\n 3 female Netba~ 3.9 6.3 35.9 12.1 78 20.1 70 15.0 57.3\n 4 female Row 3.91 7.3 37.6 12.9 43 22.3 126. 25.2 54.8\n 5 female Netba~ 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1\n 6 female Row 3.95 3.3 36.9 12.5 40 24.5 74.9 16.4 63.0\n 7 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3\n 8 female Netba~ 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0\n 9 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n10 female Netba~ 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3\n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Descending order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(BMI))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 male Field 5.48 6.2 48.2 16.3 94 34.4 82.7 13.9 106 \n 2 male Field 4.96 8.3 45.3 15.7 141 33.7 114. 17.4 89 \n 3 male Field 5.48 4.6 49.4 18 132 32.5 55.7 8.51 102 \n 4 female Field 4.75 7.5 43.8 15.2 90 31.9 132. 23.0 73.0\n 5 male Field 5.01 8.9 46 15.9 212 30.2 112. 19.9 78 \n 6 male Field 5.01 8.9 46 15.9 212 30.2 96.9 18.1 80 \n 7 male Field 5.09 8.9 46.3 15.4 44 30.0 71.1 14.0 88 \n 8 female Field 4.58 5.8 42.1 14.7 164 28.6 110. 21.3 68.9\n 9 female Field 4.51 9 39.7 14.3 36 28.1 136. 24.9 63.0\n10 male WPolo 5.34 6.2 49.8 17.2 143 27.8 75.7 13.5 82 \n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## “The top ones”\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:7) %>%\n select(Sport, Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n Sport Wt\n \n1 Field 123.\n2 BBall 114.\n3 Field 111.\n4 Field 108.\n5 Field 103.\n6 WPolo 101 \n7 BBall 100.\n```\n:::\n:::\n\n\n\n## Another way\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice_max(order_by = Wt, n=7) %>% \n select(Sport, Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n Sport Wt\n \n1 Field 123.\n2 BBall 114.\n3 Field 111.\n4 Field 108.\n5 Field 103.\n6 WPolo 101 \n7 BBall 100.\n```\n:::\n:::\n\n\n\n\n## Create new variables from old ones\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n mutate(wt_lb = Wt * 2.2) %>%\n select(Sport, Sex, Wt, wt_lb) %>% \n arrange(Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sport Sex Wt wt_lb\n \n 1 Gym female 37.8 83.2\n 2 Gym female 43.8 96.4\n 3 Gym female 45.1 99.2\n 4 Tennis female 45.8 101. \n 5 Tennis female 47.4 104. \n 6 Gym female 47.8 105. \n 7 T400m female 49.2 108. \n 8 Row female 49.8 110. \n 9 T400m female 50.9 112. \n10 Netball female 51.9 114. \n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Turning the result into a number\nOutput is always data frame unless you explicitly turn it into something\nelse, eg. the weight of the heaviest athlete, as a number:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(Wt)) %>% pluck(\"Wt\", 1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.2\n```\n:::\n:::\n\n\n\nOr the 20 heaviest weights in descending order:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pluck(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90\n[10] 97.90 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60\n[19] 94.25 94.20\n```\n:::\n:::\n\n\n\n## Another way to do the last one\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pull(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90\n[10] 97.90 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60\n[19] 94.25 94.20\n```\n:::\n:::\n\n\n\n`pull` grabs the column you name *as a vector* (of whatever it contains).\n\n## To find the mean height of the women athletes\nTwo ways:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% group_by(Sex) %>% summarize(m = mean(Ht))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n Sex m\n \n1 female 175.\n2 male 186.\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n filter(Sex == \"female\") %>%\n summarize(m = mean(Ht))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n m\n \n1 175.\n```\n:::\n:::\n\n\n\n\\normalsize\n\n## Summary of data selection/arrangement \"verbs\" {.smaller}\n\n | Verb | Purpose|\n |:-----|:-------------------------------|\n |`select` | Choose columns|\n |`slice` | Choose rows by number|\n |`slice_sample` | Choose random rows |\n |`slice_max` | Choose rows with largest values on a variable (also `slice_min`) |\n |`filter` | Choose rows satisfying conditions|\n | `arrange` | Sort in order by column(s) |\n | `mutate` | Create new variables | \n | `group_by` | Create groups to work with|\n |`summarize` | Calculate summary statistics (by groups if defined) |\n | `pluck` | Extract items from data frame |\n | `pull` | Extract a single column from a data frame as a vector|\n\n \n## Looking things up in another data frame\n\n- Suppose you are working in the nails department of a hardware store and you find that you have sold these items:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_sales.csv\"\nsales <- read_csv(my_url)\nsales\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n product_code sales\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\n\n## Product descriptions and prices\n\n- but you don't remember what these product codes are, and you would like to know the total revenue from these sales.\n\n- Fortunately you found a list of product descriptions and prices:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_desc.csv\"\ndesc <- read_csv(my_url)\ndesc\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n product_code description size qty price\n \n1 061-4525-2 spike nail \"10\\\"\" 1 1.49\n2 061-5329-4 masonry nail \"1.5\\\"\" 112 8.19\n3 061-5344-6 finishing nail \"1\\\"\" 1298 6.99\n4 061-5375-2 roofing nail \"1.25\\\"\" 192 6.99\n5 061-5388-2 framing nail \"4\\\"\" 25 8.19\n6 161-0090-0 wood nail \"1\\\"\" 25 2.39\n7 161-0199-4 panel nail \"1-5/8\\\"\" 20 4.69\n```\n:::\n:::\n\n\n\n- the `size` values are measured in inches (symbol `\"`), but R uses the same symbol for the start and end of text, so the `\"` representing \"inches\" is \"escaped\". Hence the odd look.\n\n\\normalsize\n\n## The lookup\n\n- How do you \"look up\" the product codes to find the product descriptions and prices?\n- `left_join`.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n product_code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n## What we have\n\n- this looks up all the rows in the *first* dataframe that are also in the *second*. \n- by default matches all columns with same name in two dataframes (`product_code` here)\n- get *all* columns in *both* dataframes. The rows are the ones for that `product_code`.\n\nSo now can work out how much the total revenue was:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc) %>% \n mutate(product_revenue = sales*price) %>% \n summarize(total_revenue = sum(product_revenue))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n total_revenue\n \n1 178.\n```\n:::\n:::\n\n\n\n\n## More comments\n\n- if any product codes are not matched, you get NA in the added columns\n- anything in the *second* dataframe that was not in the first does not appear (here, any products that were not sold)\n- other variations (examples follow):\n - if there are two columns with the same name in the two dataframes, and you only want to match on one, use `by` with one column name\n - if the columns you want to look up have different names in the two dataframes, use `by` with a \"named list\"\n\n## Matching on only some matching names\n\n- Suppose the `sales` dataframe *also* had a column `qty` (which was the quantity sold): \n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"qty\"=\"sales\") -> sales1\nsales1\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n product_code qty\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\\normalsize\n\n- The `qty` in `sales1` is the quantity sold, but the `qty` in `desc` is the number of nails in a package. These should *not* be matched: they are different things.\n\n## Matching only on product code\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales1 %>% \n left_join(desc, join_by(product_code))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n product_code qty.x description size qty.y price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n- Get `qty.x` (from `sales1`) and `qty.y` (from `desc`).\n\n## Matching on different names 1/2\n\n- Suppose the product code in `sales` was just `code`:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"code\" = \"product_code\") -> sales2\nsales2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n code sales\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\\normalsize\n\n- How to match the two product codes that have different names?\n\n## Matching on different names 2/2\n\n- Use `by`, but like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales2 %>% \n left_join(desc, join_by(code == product_code))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n## Other types of join\n\n- `right_join`: interchanges roles, looking up keys from second dataframe in first.\n- `anti_join`: give me all the rows in the first dataframe that are *not* in the second. (Use this eg. to see whether the product descriptions are incomplete.)\n- `full_join`: give me all the rows in both dataframes, with missings as needed.\n\n## Full join here\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% full_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 6\n product_code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n7 061-5329-4 NA masonry nail \"1.5\\\"\" 112 8.19\n```\n:::\n:::\n\n\n\\normalsize\n\n- The missing `sales` for \"masonry nail\" says that it was in the lookup table `desc`, but we didn't sell any.\n\n\n## The same thing, but with `anti_join`\n\nAnything in first df but not in second?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesc %>% anti_join(sales)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 5\n product_code description size qty price\n \n1 061-5329-4 masonry nail \"1.5\\\"\" 112 8.19\n```\n:::\n:::\n\n\n\nMasonry nails are the only thing in our product description file that we did not sell any of.\n\n", + "markdown": "---\ntitle: \"Choosing things in dataframes\"\n---\n\n\n\n## Packages\n\nThe usual:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Doing things with data frames\n\nLet's go back to our Australian athletes:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 female Netball 4.56 13.3 42.2 13.6 20 19.2 49 11.3 53.1 177.\n 2 female Netball 4.15 6 38 12.7 59 21.2 110. 25.3 47.1 173.\n 3 female Netball 4.16 7.6 37.5 12.3 22 21.4 89 19.4 53.4 176 \n 4 female Netball 4.32 6.4 37.7 12.3 30 21.0 98.3 19.6 48.8 170.\n 5 female Netball 4.06 5.8 38.7 12.8 78 21.8 122. 23.1 56.0 183 \n 6 female Netball 4.12 6.1 36.6 11.8 21 21.4 90.4 16.9 56.4 178.\n 7 female Netball 4.17 5 37.4 12.7 109 21.5 107. 21.3 53.1 177.\n 8 female Netball 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4 174.\n 9 female Netball 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0 174.\n10 female Netball 4.44 9.7 41.4 14.1 64 22.8 126. 25.0 51.6 174.\n# i 192 more rows\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Choosing a column\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 1\n Sport \n \n 1 Netball\n 2 Netball\n 3 Netball\n 4 Netball\n 5 Netball\n 6 Netball\n 7 Netball\n 8 Netball\n 9 Netball\n10 Netball\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing several columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport, Hg, BMI)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sport Hg BMI\n \n 1 Netball 13.6 19.2\n 2 Netball 12.7 21.2\n 3 Netball 12.3 21.4\n 4 Netball 12.3 21.0\n 5 Netball 12.8 21.8\n 6 Netball 11.8 21.4\n 7 Netball 12.7 21.5\n 8 Netball 12.4 24.4\n 9 Netball 12.4 22.6\n10 Netball 14.1 22.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing consecAutive columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sex:WCC)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sex Sport RCC WCC\n \n 1 female Netball 4.56 13.3\n 2 female Netball 4.15 6 \n 3 female Netball 4.16 7.6\n 4 female Netball 4.32 6.4\n 5 female Netball 4.06 5.8\n 6 female Netball 4.12 6.1\n 7 female Netball 4.17 5 \n 8 female Netball 3.8 6.6\n 9 female Netball 3.96 5.5\n10 female Netball 4.44 9.7\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing all-but some columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(-(RCC:LBM))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sex Sport Ht Wt\n \n 1 female Netball 177. 59.9\n 2 female Netball 173. 63 \n 3 female Netball 176 66.3\n 4 female Netball 170. 60.7\n 5 female Netball 183 72.9\n 6 female Netball 178. 67.9\n 7 female Netball 177. 67.5\n 8 female Netball 174. 74.1\n 9 female Netball 174. 68.2\n10 female Netball 174. 68.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Select-helpers\n\nOther ways to select columns: those whose name:\n\n- `starts_with` something\n- `ends_with` something\n- `contains` something\n- `matches` a \"regular expression\"\n- `everything()` select all the columns\n\n## Columns whose names Abegin with S\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(starts_with(\"S\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sex Sport SSF\n \n 1 female Netball 49 \n 2 female Netball 110. \n 3 female Netball 89 \n 4 female Netball 98.3\n 5 female Netball 122. \n 6 female Netball 90.4\n 7 female Netball 107. \n 8 female Netball 157. \n 9 female Netball 101. \n10 female Netball 126. \n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Columns whose names end with C\n\neither uppercase or lowercase:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"c\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n RCC WCC Hc\n \n 1 4.56 13.3 42.2\n 2 4.15 6 38 \n 3 4.16 7.6 37.5\n 4 4.32 6.4 37.7\n 5 4.06 5.8 38.7\n 6 4.12 6.1 36.6\n 7 4.17 5 37.4\n 8 3.8 6.6 36.5\n 9 3.96 5.5 36.3\n10 4.44 9.7 41.4\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Case-sensitive\n\nThis works with any of the select-helpers:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"C\", ignore.case=FALSE))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n RCC WCC\n \n 1 4.56 13.3\n 2 4.15 6 \n 3 4.16 7.6\n 4 4.32 6.4\n 5 4.06 5.8\n 6 4.12 6.1\n 7 4.17 5 \n 8 3.8 6.6\n 9 3.96 5.5\n10 4.44 9.7\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Column names containing letter R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(contains(\"r\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sport RCC Ferr\n \n 1 Netball 4.56 20\n 2 Netball 4.15 59\n 3 Netball 4.16 22\n 4 Netball 4.32 30\n 5 Netball 4.06 78\n 6 Netball 4.12 21\n 7 Netball 4.17 109\n 8 Netball 3.8 102\n 9 Netball 3.96 71\n10 Netball 4.44 64\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Exactly two characters, ending with T\n\nIn regular expression terms, this is `^.t$`:\n\n- `^` means \"start of text\"\n- `.` means \"exactly one character, but could be anything\"\n- `$` means \"end of text\".\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(matches(\"^.t$\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n Ht Wt\n \n 1 177. 59.9\n 2 173. 63 \n 3 176 66.3\n 4 170. 60.7\n 5 183 72.9\n 6 178. 67.9\n 7 177. 67.5\n 8 174. 74.1\n 9 174. 68.2\n10 174. 68.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing columns by property\n\n- Use `where` as with summarizing several columns\n- eg, to choose text columns:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(where(is.character))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n Sex Sport \n \n 1 female Netball\n 2 female Netball\n 3 female Netball\n 4 female Netball\n 5 female Netball\n 6 female Netball\n 7 female Netball\n 8 female Netball\n 9 female Netball\n10 female Netball\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing rows by number\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice(16:25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 female Netball 4.25 10.7 39.5 13.2 127 24.5 157. 26.5 54.5 174 \n 2 female Netball 4.46 10.9 39.7 13.7 102 24.0 116. 23.0 57.2 176 \n 3 female Netball 4.4 9.3 40.4 13.6 86 26.2 182. 30.1 54.4 172.\n 4 female Netball 4.83 8.4 41.8 13.4 40 20.0 71.6 13.9 57.6 183.\n 5 female Netball 4.23 6.9 38.3 12.6 50 25.7 144. 26.6 61.5 180.\n 6 female Netball 4.24 8.4 37.6 12.5 58 25.6 201. 35.5 53.5 180.\n 7 female Netball 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1 180.\n 8 female Netball 4.03 8.5 37.7 13 51 23.4 104. 19.6 55.4 172.\n 9 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3 196.\n10 female BBall 4.41 8.3 38.2 12.7 68 20.7 103. 21.3 58.6 190.\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Non-consecutive rows\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice(10, 13, 17, 42)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n1 female Netball 4.44 9.7 41.4 14.1 64 22.8 126. 25.0 51.6 174.\n2 female Netball 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3 174.\n3 female Netball 4.46 10.9 39.7 13.7 102 24.0 116. 23.0 57.2 176 \n4 female Row 4.37 8.1 41.8 14.3 53 23.5 98 21.8 63.0 185.\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## A random sample of rows\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice_sample(n=8)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n1 female T400m 4.2 6.5 39.1 13 51 20.1 36.8 9.91 47.6 162 \n2 female Row 4.57 6.6 42.8 14.5 85 20.8 114. 21.5 52.8 180.\n3 male TSprnt 4.64 9 42.9 14.9 122 24.0 38.9 7.52 77 186 \n4 male Field 5.11 9.6 48.2 16.7 103 27.4 65.9 11.7 83 186.\n5 male Row 5.18 6.5 45.4 14.9 93 19.7 54 10.8 48 165.\n6 female Row 4.51 8.3 43.7 14.7 34 21.3 69.9 18.0 56.3 180.\n7 female BBall 4.14 5 36.4 11.6 21 21.9 105. 19.9 55.4 178.\n8 female Row 4.41 5.9 41.1 13.5 41 24.0 124. 22.4 61.7 182.\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Rows for which something is true\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6 168.\n 2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0 178.\n 3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5 162.\n 4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8 172.\n 5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2 167.\n 6 female Tennis 5.16 7.2 44.3 14.5 88 18.3 61.9 12.9 48.8 175 \n 7 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9 158.\n 8 male Tennis 5.66 8.3 50.2 17.7 38 23.8 56.5 10.0 72 184.\n 9 male Tennis 5.03 6.4 42.7 14.3 122 22.0 47.6 8.51 68 183.\n10 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 178.\n11 male Tennis 5.38 6.3 46 15.7 32 21.1 34.9 6.26 72 191.\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## More complicated selections\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\", RCC < 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6 168.\n2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0 178.\n3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5 162.\n4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8 172.\n5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2 167.\n6 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9 158.\n7 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 178.\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Another way to do \"and\"\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\") %>% \n filter(RCC < 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6 168.\n2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0 178.\n3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5 162.\n4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8 172.\n5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2 167.\n6 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9 158.\n7 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 178.\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Either/Or\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\" | RCC > 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 66 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 female Row 5.02 6.4 44.8 15.2 48 19.8 91 19.2 53.6 183.\n 2 female T400m 5.31 9.5 47.1 15.9 29 21.4 57.9 11.1 57.5 174.\n 3 female Field 5.33 9.3 47 15 62 25.3 103. 19.5 59.9 172.\n 4 female TSprnt 5.16 8.2 45.3 14.7 34 20.3 46.1 10.2 51.5 168 \n 5 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6 168.\n 6 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0 178.\n 7 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5 162.\n 8 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8 172.\n 9 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2 167.\n10 female Tennis 5.16 7.2 44.3 14.5 88 18.3 61.9 12.9 48.8 175 \n# i 56 more rows\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Sorting into order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 female Netball 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4 174.\n 2 female Netball 3.9 6.3 35.9 12.1 78 20.1 70 15.0 57.3 183.\n 3 female T400m 3.9 6 38.9 13.5 16 19.4 48.4 10.5 53.7 176 \n 4 female Row 3.91 7.3 37.6 12.9 43 22.3 126. 25.2 54.8 181.\n 5 female Netball 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1 180.\n 6 female Row 3.95 3.3 36.9 12.5 40 24.5 74.9 16.4 63.0 175.\n 7 female Netball 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0 174.\n 8 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3 196.\n 9 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6 168.\n10 female Netball 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3 174.\n# i 192 more rows\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Breaking ties by another variable\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC, BMI)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 female Netball 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4 174.\n 2 female T400m 3.9 6 38.9 13.5 16 19.4 48.4 10.5 53.7 176 \n 3 female Netball 3.9 6.3 35.9 12.1 78 20.1 70 15.0 57.3 183.\n 4 female Row 3.91 7.3 37.6 12.9 43 22.3 126. 25.2 54.8 181.\n 5 female Netball 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1 180.\n 6 female Row 3.95 3.3 36.9 12.5 40 24.5 74.9 16.4 63.0 175.\n 7 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3 196.\n 8 female Netball 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0 174.\n 9 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6 168.\n10 female Netball 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3 174.\n# i 192 more rows\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## Descending order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(BMI))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM Ht\n \n 1 male Field 5.48 6.2 48.2 16.3 94 34.4 82.7 13.9 106 189.\n 2 male Field 4.96 8.3 45.3 15.7 141 33.7 114. 17.4 89 179.\n 3 male Field 5.48 4.6 49.4 18 132 32.5 55.7 8.51 102 185 \n 4 female Field 4.75 7.5 43.8 15.2 90 31.9 132. 23.0 73.0 172.\n 5 male Field 5.01 8.9 46 15.9 212 30.2 112. 19.9 78 180.\n 6 male Field 5.01 8.9 46 15.9 212 30.2 96.9 18.1 80 180.\n 7 male Field 5.09 8.9 46.3 15.4 44 30.0 71.1 14.0 88 185.\n 8 female Field 4.58 5.8 42.1 14.7 164 28.6 110. 21.3 68.9 175 \n 9 female Field 4.51 9 39.7 14.3 36 28.1 136. 24.9 63.0 173.\n10 male WPolo 5.34 6.2 49.8 17.2 143 27.8 75.7 13.5 82 185.\n# i 192 more rows\n# i 1 more variable: Wt \n```\n:::\n:::\n\n\n\n## \"The top ones\"\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:7) %>%\n select(Sport, Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n Sport Wt\n \n1 Field 123.\n2 BBall 114.\n3 Field 111.\n4 Field 108.\n5 Field 103.\n6 WPolo 101 \n7 BBall 100.\n```\n:::\n:::\n\n\n\n## Another way\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice_max(order_by = Wt, n=7) %>% \n select(Sport, Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n Sport Wt\n \n1 Field 123.\n2 BBall 114.\n3 Field 111.\n4 Field 108.\n5 Field 103.\n6 WPolo 101 \n7 BBall 100.\n```\n:::\n:::\n\n\n\n## Create new variables from old ones\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n mutate(wt_lb = Wt * 2.2) %>%\n select(Sport, Sex, Wt, wt_lb) %>% \n arrange(Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sport Sex Wt wt_lb\n \n 1 Gym female 37.8 83.2\n 2 Gym female 43.8 96.4\n 3 Gym female 45.1 99.2\n 4 Tennis female 45.8 101. \n 5 Tennis female 47.4 104. \n 6 Gym female 47.8 105. \n 7 T400m female 49.2 108. \n 8 Row female 49.8 110. \n 9 T400m female 50.9 112. \n10 Netball female 51.9 114. \n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Turning the result into a number\n\nOutput is always data frame unless you explicitly turn it into something\nelse, eg. the weight of the heaviest athlete, as a number:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(Wt)) %>% pluck(\"Wt\", 1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.2\n```\n:::\n:::\n\n\n\nOr the 20 heaviest weights in descending order:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pluck(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90 97.90\n[11] 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60 94.25 94.20\n```\n:::\n:::\n\n\n\n## Another way to do the last one\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pull(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90 97.90\n[11] 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60 94.25 94.20\n```\n:::\n:::\n\n\n\n`pull` grabs the column you name *as a vector* (of whatever it\ncontains).\n\n## To find the mean height of the women athletes\n\nTwo ways:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% group_by(Sex) %>% summarize(m = mean(Ht))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n Sex m\n \n1 female 175.\n2 male 186.\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n filter(Sex == \"female\") %>%\n summarize(m = mean(Ht))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n m\n \n1 175.\n```\n:::\n:::\n\n\n\n\\normalsize\n\n## Summary of data selection/arrangement \"verbs\" {.smaller}\n\n| Verb | Purpose |\n|:---------------|:-------------------------------------------------------|\n| `select` | Choose columns |\n| `slice` | Choose rows by number |\n| `slice_sample` | Choose random rows |\n| `slice_max` | Choose rows with largest values on a variable (also `slice_min`) |\n| `filter` | Choose rows satisfying conditions |\n| `arrange` | Sort in order by column(s) |\n| `mutate` | Create new variables |\n| `group_by` | Create groups to work with |\n| `summarize` | Calculate summary statistics (by groups if defined) |\n| `pluck` | Extract items from data frame |\n| `pull` | Extract a single column from a data frame as a vector |\n\n## Looking things up in another data frame\n\n- Suppose you are working in the nails department of a hardware store\n and you find that you have sold these items:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_sales.csv\"\nsales <- read_csv(my_url)\nsales\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n product_code sales\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\n## Product descriptions and prices\n\n- but you don't remember what these product codes are, and you would\n like to know the total revenue from these sales.\n\n- Fortunately you found a list of product descriptions and prices:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_desc.csv\"\ndesc <- read_csv(my_url)\ndesc\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n product_code description size qty price\n \n1 061-4525-2 spike nail \"10\\\"\" 1 1.49\n2 061-5329-4 masonry nail \"1.5\\\"\" 112 8.19\n3 061-5344-6 finishing nail \"1\\\"\" 1298 6.99\n4 061-5375-2 roofing nail \"1.25\\\"\" 192 6.99\n5 061-5388-2 framing nail \"4\\\"\" 25 8.19\n6 161-0090-0 wood nail \"1\\\"\" 25 2.39\n7 161-0199-4 panel nail \"1-5/8\\\"\" 20 4.69\n```\n:::\n:::\n\n\n\n- the `size` values are measured in inches (symbol `\"`), but R uses\n the same symbol for the start and end of text, so the `\"`\n representing \"inches\" is \"escaped\". Hence the odd look.\n\n\\normalsize\n\n## The lookup\n\n- How do you \"look up\" the product codes to find the product\n descriptions and prices?\n- `left_join`.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n product_code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n## What we have\n\n- this looks up all the rows in the *first* dataframe that are also in\n the *second*.\n- by default matches all columns with same name in two dataframes\n (`product_code` here)\n- get *all* columns in *both* dataframes. The rows are the ones for\n that `product_code`.\n\nSo now can work out how much the total revenue was:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc) %>% \n mutate(product_revenue = sales*price) %>% \n summarize(total_revenue = sum(product_revenue))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n total_revenue\n \n1 178.\n```\n:::\n:::\n\n\n\n## More comments\n\n- if any product codes are not matched, you get NA in the added\n columns\n- anything in the *second* dataframe that was not in the first does\n not appear (here, any products that were not sold)\n- other variations (examples follow):\n - if there are two columns with the same name in the two\n dataframes, and you only want to match on one, use `by` with one\n column name\n - if the columns you want to look up have different names in the\n two dataframes, use `by` with a \"named list\"\n\n## Matching on only some matching names\n\n- Suppose the `sales` dataframe *also* had a column `qty` (which was\n the quantity sold):\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"qty\"=\"sales\") -> sales1\nsales1\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n product_code qty\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- The `qty` in `sales1` is the quantity sold, but the `qty` in `desc`\n is the number of nails in a package. These should *not* be matched:\n they are different things.\n\n## Matching only on product code\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales1 %>% \n left_join(desc, join_by(product_code))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n product_code qty.x description size qty.y price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsales1 %>% \n left_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 5\n product_code qty description size price\n \n1 061-5344-6 10 NA\n2 161-0090-0 6 NA\n3 061-5388-2 2 NA\n4 161-0199-4 8 NA\n5 061-5375-2 5 NA\n6 061-4525-2 3 NA\n```\n:::\n:::\n\n\n\n- Get `qty.x` (from `sales1`) and `qty.y` (from `desc`).\n\n## Matching on different names 1/2\n\n- Suppose the product code in `sales` was just `code`:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"code\" = \"product_code\") -> sales2\nsales2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n code sales\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- How to match the two product codes that have different names?\n\n## Matching on different names 2/2\n\n- Use `join_by`, but like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales2 %>% \n left_join(desc, join_by(code == product_code))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n## Other types of join\n\n- `right_join`: interchanges roles, looking up keys from second\n dataframe in first.\n- `anti_join`: give me all the rows in the first dataframe that are\n *not* in the second. (Use this eg. to see whether the product\n descriptions are incomplete.)\n- `full_join`: give me all the rows in both dataframes, with missings\n as needed.\n\n## Full join here\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% full_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 6\n product_code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n7 061-5329-4 NA masonry nail \"1.5\\\"\" 112 8.19\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- The missing `sales` for \"masonry nail\" says that it was in the\n lookup table `desc`, but we didn't sell any.\n\n## The same thing, but with `anti_join`\n\nAnything in first df but not in second?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesc %>% anti_join(sales)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 5\n product_code description size qty price\n \n1 061-5329-4 masonry nail \"1.5\\\"\" 112 8.19\n```\n:::\n\n```{.r .cell-code}\nsales %>% anti_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 0 x 2\n# i 2 variables: product_code , sales \n```\n:::\n:::\n\n\n\nMasonry nails are the only thing in our product description file that we\ndid not sell any of.\n", "supporting": [ "choosing_files" ], diff --git a/_freeze/functions/execute-results/html.json b/_freeze/functions/execute-results/html.json index 381519c..8c5a89a 100644 --- a/_freeze/functions/execute-results/html.json +++ b/_freeze/functions/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "85a3c227148dfefa4d5d86603ab0f099", + "hash": "892a465f0a928b45c1aa747892880c5f", "result": { - "markdown": "---\ntitle: \"Functions\"\n---\n\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n## Don’t repeat yourself\n- See this:\n\n::: {.cell}\n\n```{.r .cell-code}\na <- 50\nb <- 11\nd <- 3\nas <- sqrt(a - 1)\nas\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nbs <- sqrt(b - 1)\nbs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nds <- sqrt(d - 1)\nds\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n## What's the problem?\n\n- Same calculation done three different times, by copying, pasting and\nediting.\n- Dangerous: what if you forget to change something after you pasted?\n\n- Programming principle: \"don't repeat yourself\".\n- Hadley Wickham: don't copy-paste more than twice.\n- Instead: *write a function*. \n\n## Anatomy of function\n- Header line with function name and input value(s).\n- Body with calculation of values to output/return.\n- Return value: the output from function.\nIn our case:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n ans <- sqrt(x - 1)\n return(ans)\n}\n```\n:::\n\n\nor more simply (\"the R way\", better style)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n sqrt(x - 1)\n}\n```\n:::\n\n\nIf last line of function calculates value without saving it, that value is\nreturned.\n\n## About the input; testing\n- The input to a function can be called anything. Here we called it `x`.\nThis is the name used inside the function.\n- The function is a “machine” for calculating square-root-minus-1. It\ndoesn’t do anything until you call it:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(50)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(11)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n- It works!\n\n## Vectorization 1/2 \n\n- We conceived our function to work on numbers:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(3.25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.5\n```\n:::\n:::\n\n\n- but it actually works on vectors too, as a free bonus of R:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(c(50, 11, 3))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.000000 3.162278 1.414214\n```\n:::\n:::\n\n\n- or... (over)\n\n## Vectorization 2/2 \n\n- or even data frames:\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- tibble(x = 1:2, y = 3:4)\nsqrt_minus_1(d)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## More than one input\n\n- Allow the value to be subtracted, before taking square root, to be\ninput to function as well, thus:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d) {\n sqrt(x - d)\n}\n```\n:::\n\n\n- Call the function with the x and d inputs in the right order:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n\n- or give the inputs names, in which case they can be in *any order*:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(d = 2, x = 51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n\n## Defaults 1/2\n- Many R functions have values that you can change if you want to,\nbut usually you don’t want to, for example:\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- c(3, 4, 5, NA, 6, 7)\nmean(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NA\n```\n:::\n\n```{.r .cell-code}\nmean(x, na.rm = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5\n```\n:::\n:::\n\n\n- By default, the mean of data with a missing value is missing, but if\nyou specify `na.rm=TRUE`, the missing values are removed before the mean\nis calculated.\n\n- That is, `na.rm` has a default value of `FALSE`: that’s what it will be unless\nyou change it.\n\n## Defaults 2/2\n- In our function, set a default value for d like this:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n sqrt(x - d)\n}\n```\n:::\n\n\n- If you specify a value for d, it will be used. If you don't, 1 will be used instead: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_value(51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.071068\n```\n:::\n:::\n\n\n## Catching errors before they happen\n- What happened here?\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-stderr}\n```\nWarning in sqrt(x - d): NaNs produced\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NaN\n```\n:::\n:::\n\n\n- Message not helpful. Actually, function tried to take square root of\nnegative number.\n- In fact, not even error, just warning.\n- Check that the square root will be OK first. Here’s how:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n stopifnot(x - d >= 0)\n sqrt(x - d)\n}\n```\n:::\n\n\n## What happens with `stopifnot`\n- This should be good, and is:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(8, 6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n- This should fail, and see how it does:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in sqrt_minus_value(6, 8): x - d >= 0 is not TRUE\n```\n:::\n:::\n\n\n- Where the function fails, we get informative error, but if everything\ngood, the `stopifnot` does nothing.\n- `stopifnot` contains one or more logical conditions, and all of them\nhave to be true for function to work. So put in everything that you\nwant to be true.\n\n## Using R’s built-ins\n- When you write a function, you can use anything built-in to R, or\neven any functions that you defined before.\n- For example, if you will be calculating a lot of regression-line slopes,\nyou don’t have to do this from scratch: you can use R’s regression\ncalculations, like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df <- tibble(x = 1:4, y = c(10, 11, 10, 14))\n# my_df\nmy_df.1 <- lm(y ~ x, data = my_df)\n# summary(my_df.1)\ntidy(my_df.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Pulling out just the slope\n\nUse `pluck`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(my_df.1) %>% pluck(\"estimate\", 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n\n## Making this into a function\n- First step: make sure you have it working without a function (we do)\n- Inputs: two, an `x` and a `y`.\n- Output: just the slope, a number. Thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy) {\n y.1 <- lm(yy ~ xx)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n- Check using our data from before: correct:\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n## Passing things on\n- `lm` has a lot of options, with defaults, that we might want to change.\nInstead of intercepting all the possibilities and passing them on, we\ncan do this:\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy, ...) {\n y.1 <- lm(yy ~ xx, ...)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n- The `...` in the header line means “accept any other input”, and the\n`...` in the `lm` line means “pass anything other than `x` and `y` straight\non to `lm`”.\n\n\n## Using `...`\n- One of the things `lm` will accept is a vector called `subset` containing\nthe list of observations to include in the regression.\n- So we should be able to do this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y, subset = 3:4))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 4\n```\n:::\n:::\n\n\n\n- Just uses the last two observations in `x` and `y`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df %>% slice(3:4)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n- so the slope should\nbe $(14 − 10)/(4 − 3) = 4$ and is.\n\n\n\n## Running a function for each of several inputs\n- Suppose we have a data frame containing several different `x`’s to use\nin regressions, along with the `y` we had before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(x1 = 1:4, x2 = c(8, 7, 6, 5), x3 = c(2, 4, 6, 9)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Want to use these as different x’s for a regression with `y` from `my_df` as the\nresponse, and collect together the three different slopes.\n- Python-like way: a `for` loop.\n- R-like way: `map_dbl`: less coding, but more thinking.\n\n## The loop way\n- “Pull out” column `i` of data frame `d` as `d %>% pull(i)`.\n- Create empty vector `slopes` to store the slopes.\n- Looping variable `i` goes from 1 to 3 (3 columns, thus 3 slopes):\n\n::: {.cell}\n\n```{.r .cell-code}\nslopes <- numeric(3)\nfor (i in 1:3) {\n d %>% pull(i) -> xx\n slopes[i] <- slope(xx, my_df$y)\n}\nslopes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1000000 -1.1000000 0.5140187\n```\n:::\n:::\n\n\n- Check this by doing the three `lm`s, one at a time.\n\n## The `map_dbl` way\n- In words: for each of these (columns of `d`), run function (`slope`) with inputs\n\"it\" and `y`), and collect together the answers.\n- Since slope returns a decimal number (a `dbl`), appropriate\nfunction-running function is `map_dbl`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) slope(d, my_df$y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n 1.1000000 -1.1000000 0.5140187 \n```\n:::\n:::\n\n\n- Same as loop, with a lot less coding.\n\n## Square roots\n\n- “Find the square roots of each of the numbers 1 through 10”:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 1:10\nmap_dbl(x, \\(x) sqrt(x))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427\n [9] 3.000000 3.162278\n```\n:::\n:::\n\n\n## Summarizing all columns of a data frame, two ways\n\n- use my `d` from above: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) mean(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n2.50 6.50 5.25 \n```\n:::\n\n```{.r .cell-code}\nd %>% summarize(across(everything(), \\(x) mean(x)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThe mean of each column, with the columns labelled. \n\n## What if summary returns more than one thing?\n- For example, finding quartiles:\n\n::: {.cell}\n\n```{.r .cell-code}\nquartiles <- function(x) {\n quantile(x, c(0.25, 0.75))\n}\nquartiles(1:5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n25% 75% \n 2 4 \n```\n:::\n:::\n\n\n- When function returns more than one thing, `map` (or `map_df`) instead\nof `map_dbl`.\n\n## map results\n- Try:\n\n::: {.cell}\n\n```{.r .cell-code}\nmap(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$x1\n 25% 75% \n1.75 3.25 \n\n$x2\n 25% 75% \n5.75 7.25 \n\n$x3\n 25% 75% \n3.50 6.75 \n```\n:::\n:::\n\n\n- A list. \n\n## Or\n\n- Better: pretend output from quartiles is one-column data\nframe: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_df(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Or even\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% map_df(\\(d) quartiles(d))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- This works because the implicit first thing in map is (the columns of) the\ndata frame that came out of the previous step.\n- These are 1st and 3rd quartiles of each column of `d`, according to R’s\ndefault definition (see help for `quantile`).\n\n## `Map` in data frames with `mutate`\n- `map` can also be used within data frames to calculate new columns.\nLet’s do the square roots of 1 through 10 again:\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- tibble(x = 1:10)\nd %>% mutate(root = map_dbl(x, \\(x) sqrt(x)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Write a function first and then map it\n- If the “for each” part is simple, go ahead and use `map_`-whatever.\n- If not, write a function to do the complicated thing first.\n- Example: “half or triple plus one”: if the input is an even number,\nhalve it; if it is an odd number, multiply it by three and add one.\n- This is hard to do as a one-liner: first we have to figure out whether\nthe input is odd or even, and then we have to do the right thing with\nit.\n\n## Odd or even?\n\n\n- Odd or even? Work out the remainder when dividing by 2:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n6 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0\n```\n:::\n\n```{.r .cell-code}\n5 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1\n```\n:::\n:::\n\n\n- 5 has remainder 1 so it is odd.\n\n## Write the function\n- First test for integerness, then test for odd or even, and then do the appropriate calculation:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo <- function(x) {\n stopifnot(round(x) == x) # passes if input an integer\n remainder <- x %% 2\n if (remainder == 1) {\n ans <- 3 * x + 1\n }\n else {\n ans <- x %/% 2 # integer division\n }\n ans\n}\n```\n:::\n\n\n## Test it \n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 10\n```\n:::\n\n```{.r .cell-code}\nhotpo(12)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6\n```\n:::\n\n```{.r .cell-code}\nhotpo(4.5)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in hotpo(4.5): round(x) == x is not TRUE\n```\n:::\n:::\n\n\n\n## One through ten\n- Use a data frame of numbers 1 through 10 again:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x = 1:10) %>% mutate(y = map_int(x, \\(x) hotpo(x)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Until I get to 1 (if I ever do) {.smaller}\n- If I start from a number, find `hotpo` of it, then find `hotpo` of that,\nand keep going, what happens?\n- If I get to 4, 2, 1, 4, 2, 1 I’ll repeat for ever, so let’s stop when we get\nto 1:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq <- function(x) {\n ans <- x\n while (x != 1) {\n x <- hotpo(x)\n ans <- c(ans, x)\n }\n ans\n}\n```\n:::\n\n\n- Strategy: keep looping “while `x` is not 1”.\n- Each new `x`: add to the end of `ans`. When I hit 1, I break\nout of the `while` and return the whole `ans`. \n\n## Trying it 1/2\n- Start at 6:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6 3 10 5 16 8 4 2 1\n```\n:::\n:::\n\n\n## Trying it 2/2 \n\n- Start at 27:\n\n\n::: {.cell}\n\n:::\n\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(27)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 27 82 41 124 62 31 94 47 142 71 214\n [12] 107 322 161 484 242 121 364 182 91 274 137\n [23] 412 206 103 310 155 466 233 700 350 175 526\n [34] 263 790 395 1186 593 1780 890 445 1336 668 334\n [45] 167 502 251 754 377 1132 566 283 850 425 1276\n [56] 638 319 958 479 1438 719 2158 1079 3238 1619 4858\n [67] 2429 7288 3644 1822 911 2734 1367 4102 2051 6154 3077\n [78] 9232 4616 2308 1154 577 1732 866 433 1300 650 325\n [89] 976 488 244 122 61 184 92 46 23 70 35\n[100] 106 53 160 80 40 20 10 5 16 8 4\n[111] 2 1\n```\n:::\n:::\n\n\\normalsize\n\n\n::: {.cell}\n\n:::\n\n\n\n## Which starting points have the longest sequences?\n- The `length` of the vector returned from `hotpo_seq` says how long it\ntook to get to 1.\n- Out of the starting points 1 to 100, which one has the longest\nsequence?\n\n## Top 10 longest sequences\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:100) %>%\n mutate(seq_length = map_int(\n start, \\(start) length(hotpo_seq(start)))) %>%\n slice_max(seq_length, n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- 27 is an unusually low starting point to have such a long sequence.\n\n## What happens if we save the entire sequence? \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n- Each entry in `sequence` is itself a vector. `sequence` is a\n“list-column”.\n\n## Using the whole sequence to find its length and its max \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start))) %>%\n mutate(\n seq_length = map_int(sequence, \\(sequence) length(sequence)),\n seq_max = map_int(sequence, \\(sequence) max(sequence))\n )\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n## Does it work with `rowwise`?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start=1:7) %>% \n rowwise() %>% \n mutate(sequence = list(hotpo_seq(start))) %>% \n mutate(seq_length = length(sequence)) %>% \n mutate(seq_max = max(sequence))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nIt does.\n\n## Final thoughts on this\n- Called the **Collatz conjecture**.\n- Nobody knows whether the sequence always gets to 1.\n- Nobody has found an $n$ for which it doesn’t.\n- A [tree](https://www.jasondavies.com/collatz-graph/).\n", + "markdown": "---\ntitle: \"Functions\"\n---\n\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n# install.packages(\"vctrs\")\n```\n:::\n\n\n\n## Don’t repeat yourself\n- See this:\n\n::: {.cell}\n\n```{.r .cell-code}\na <- 50\nb <- 11\nd <- 3\nas <- sqrt(a - 1)\nas\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nbs <- sqrt(b - 1)\nbs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nds <- sqrt(d - 1)\nds\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n## What's the problem?\n\n- Same calculation done three different times, by copying, pasting and\nediting.\n- Dangerous: what if you forget to change something after you pasted?\n\n- Programming principle: \"don't repeat yourself\".\n- Hadley Wickham: don't copy-paste more than twice.\n- Instead: *write a function*. \n\n## Anatomy of function\n- Header line with function name and input value(s).\n- Body with calculation of values to output/return.\n- Return value: the output from function.\nIn our case:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n ans <- sqrt(x - 1)\n return(ans)\n}\n```\n:::\n\n\nor more simply (\"the R way\", better style)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n sqrt(x - 1)\n}\n```\n:::\n\n\nIf last line of function calculates value without saving it, that value is\nreturned.\n\n## About the input; testing\n- The input to a function can be called anything. Here we called it `x`.\nThis is the name used inside the function.\n- The function is a “machine” for calculating square-root-minus-1. It\ndoesn’t do anything until you call it:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(50)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(11)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nq <- 17\nsqrt_minus_1(q)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 4\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(\"text\")\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in x - 1: non-numeric argument to binary operator\n```\n:::\n:::\n\n\n\n- It works!\n\n## Vectorization 1/2 \n\n- We conceived our function to work on numbers:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(3.25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.5\n```\n:::\n:::\n\n\n- but it actually works on vectors too, as a free bonus of R:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(c(50, 11, 3))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.000000 3.162278 1.414214\n```\n:::\n:::\n\n\n- or... (over)\n\n## Vectorization 2/2 \n\n- or even data frames:\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- data.frame(x = 1:2, y = 3:4)\nd\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(d)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## More than one input\n\n- Allow the value to be subtracted, before taking square root, to be\ninput to function as well, thus:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d) {\n sqrt(x - d)\n}\n```\n:::\n\n\n- Call the function with the x and d inputs in the right order:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n\n- or give the inputs names, in which case they can be in *any order*:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(d = 2, x = 51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nlm(y ~ x, data = d)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = y ~ x, data = d)\n\nCoefficients:\n(Intercept) x \n 2 1 \n```\n:::\n:::\n\n\n\n## Defaults 1/2\n- Many R functions have values that you can change if you want to,\nbut usually you don’t want to, for example:\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- c(3, 4, 5, NA, 6, 7)\nmean(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NA\n```\n:::\n\n```{.r .cell-code}\nmean(x, na.rm = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5\n```\n:::\n:::\n\n\n- By default, the mean of data with a missing value is missing, but if\nyou specify `na.rm=TRUE`, the missing values are removed before the mean\nis calculated.\n\n- That is, `na.rm` has a default value of `FALSE`: that’s what it will be unless\nyou change it.\n\n## Defaults 2/2\n- In our function, set a default value for d like this:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n sqrt(x - d)\n}\n```\n:::\n\n\n- If you specify a value for d, it will be used. If you don't, 1 will be used instead: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_value(51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.071068\n```\n:::\n:::\n\n\n## Catching errors before they happen\n- What happened here?\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-stderr}\n```\nWarning in sqrt(x - d): NaNs produced\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NaN\n```\n:::\n:::\n\n\n- Message not helpful. Actually, function tried to take square root of\nnegative number.\n- In fact, not even error, just warning.\n- Check that the square root will be OK first. Here’s how:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n stopifnot(x - d >= 0)\n sqrt(x - d)\n}\n```\n:::\n\n\n## What happens with `stopifnot`\n- This should be good, and is:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(8, 6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n- This should fail, and see how it does:\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in sqrt_minus_value(6, 8): x - d >= 0 is not TRUE\n```\n:::\n:::\n\n\n- Where the function fails, we get informative error, but if everything\ngood, the `stopifnot` does nothing.\n- `stopifnot` contains one or more logical conditions, and all of them\nhave to be true for function to work. So put in everything that you\nwant to be true.\n\n## Using R’s built-ins\n- When you write a function, you can use anything built-in to R, or\neven any functions that you defined before.\n- For example, if you will be calculating a lot of regression-line slopes,\nyou don’t have to do this from scratch: you can use R’s regression\ncalculations, like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df <- data.frame(x = 1:4, y = c(10, 11, 10, 14))\nmy_df\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nmy_df.1 <- lm(y ~ x, data = my_df)\nsummary(my_df.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = y ~ x, data = my_df)\n\nResiduals:\n 1 2 3 4 \n 0.4 0.3 -1.8 1.1 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 8.5000 1.8775 4.527 0.0455 *\nx 1.1000 0.6856 1.605 0.2498 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.533 on 2 degrees of freedom\nMultiple R-squared: 0.5628,\tAdjusted R-squared: 0.3442 \nF-statistic: 2.574 on 1 and 2 DF, p-value: 0.2498\n```\n:::\n\n```{.r .cell-code}\ntidy(my_df.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Pulling out just the slope\n\nUse `pluck`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(my_df.1) %>% pluck(\"estimate\", 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n\n## Making this into a function\n- First step: make sure you have it working without a function (we do)\n- Inputs: two, an `x` and a `y`.\n- Output: just the slope, a number. Thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy) {\n y.1 <- lm(yy ~ xx)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n- Check using our data from before: correct:\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n## Passing things on\n- `lm` has a lot of options, with defaults, that we might want to change.\nInstead of intercepting all the possibilities and passing them on, we\ncan do this:\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy, ...) {\n y.1 <- lm(yy ~ xx, ...)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n- The `...` in the header line means “accept any other input”, and the\n`...` in the `lm` line means “pass anything other than `x` and `y` straight\non to `lm`”.\n\n\n## Using `...`\n- One of the things `lm` will accept is a vector called `subset` containing\nthe list of observations to include in the regression.\n- So we should be able to do this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y, subset = 3:4))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 4\n```\n:::\n:::\n\n\n\n- Just uses the last two observations in `x` and `y`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df %>% slice(3:4)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n- so the slope should\nbe $(14 − 10)/(4 − 3) = 4$ and is.\n\n\n\n## Running a function for each of several inputs\n- Suppose we have a data frame containing several different `x`’s to use\nin regressions, along with the `y` we had before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(x1 = 1:4, x2 = c(8, 7, 6, 5), x3 = c(2, 4, 6, 9)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Want to use these as different x’s for a regression with `y` from `my_df` as the\nresponse, and collect together the three different slopes.\n- Python-like way: a `for` loop.\n- R-like way: `map_dbl`: less coding, but more thinking.\n\n## The loop way\n- “Pull out” column `i` of data frame `d` as `d %>% pull(i)`.\n- Create empty vector `slopes` to store the slopes.\n- Looping variable `i` goes from 1 to 3 (3 columns, thus 3 slopes):\n\n::: {.cell}\n\n```{.r .cell-code}\nslopes <- numeric(3)\nfor (i in 1:3) {\n d %>% pull(i) -> xx\n slopes[i] <- slope(xx, my_df$y)\n}\nslopes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1000000 -1.1000000 0.5140187\n```\n:::\n:::\n\n\n- Check this by doing the three `lm`s, one at a time.\n\n## The `map_dbl` way\n- In words: for each of these (columns of `d`), run function (`slope`) with inputs\n\"it\" and `y`), and collect together the answers.\n- Since slope returns a decimal number (a `dbl`), appropriate\nfunction-running function is `map_dbl`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) slope(d, my_df$y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n 1.1000000 -1.1000000 0.5140187 \n```\n:::\n:::\n\n\n- Same as loop, with a lot less coding.\n\n## Square roots\n\n- “Find the square roots of each of the numbers 1 through 10”:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 1:10\nmap_dbl(x, \\(x) sqrt(x))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427\n [9] 3.000000 3.162278\n```\n:::\n:::\n\n\n## Summarizing all columns of a data frame, two ways\n\n- use my `d` from above: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) mean(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n2.50 6.50 5.25 \n```\n:::\n\n```{.r .cell-code}\nd %>% summarize(across(everything(), \\(x) mean(x)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThe mean of each column, with the columns labelled. \n\n## What if summary returns more than one thing?\n- For example, finding quartiles:\n\n::: {.cell}\n\n```{.r .cell-code}\nquartiles <- function(x) {\n quantile(x, c(0.25, 0.75))\n}\nquartiles(1:5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n25% 75% \n 2 4 \n```\n:::\n:::\n\n\n- When function returns more than one thing, `map` (or `map_df`) instead\nof `map_dbl`.\n\n## map results\n- Try:\n\n::: {.cell}\n\n```{.r .cell-code}\nmap(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$x1\n 25% 75% \n1.75 3.25 \n\n$x2\n 25% 75% \n5.75 7.25 \n\n$x3\n 25% 75% \n3.50 6.75 \n```\n:::\n:::\n\n\n- A list. \n\n## Or\n\n- Better: pretend output from quartiles is one-column data\nframe: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_df(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Or even\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% map_df(\\(d) quartiles(d))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- This works because the implicit first thing in map is (the columns of) the\ndata frame that came out of the previous step.\n- These are 1st and 3rd quartiles of each column of `d`, according to R’s\ndefault definition (see help for `quantile`).\n\n## `Map` in data frames with `mutate`\n- `map` can also be used within data frames to calculate new columns.\nLet’s do the square roots of 1 through 10 again:\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- tibble(x = 1:10)\nd %>% mutate(root = map_dbl(x, \\(x) sqrt(x)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Write a function first and then map it\n- If the “for each” part is simple, go ahead and use `map_`-whatever.\n- If not, write a function to do the complicated thing first.\n- Example: “half or triple plus one”: if the input is an even number,\nhalve it; if it is an odd number, multiply it by three and add one.\n- This is hard to do as a one-liner: first we have to figure out whether\nthe input is odd or even, and then we have to do the right thing with\nit.\n\n## Odd or even?\n\n\n- Odd or even? Work out the remainder when dividing by 2:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n6 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0\n```\n:::\n\n```{.r .cell-code}\n5 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1\n```\n:::\n:::\n\n\n- 5 has remainder 1 so it is odd.\n\n## Write the function\n- First test for integerness, then test for odd or even, and then do the appropriate calculation:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo <- function(x) {\n stopifnot(round(x) == x) # passes if input an integer\n remainder <- x %% 2\n if (remainder == 1) {\n ans <- 3 * x + 1\n }\n else {\n ans <- x %/% 2 # integer division\n }\n ans\n}\n```\n:::\n\n\n## Test it \n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 10\n```\n:::\n\n```{.r .cell-code}\nhotpo(12)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6\n```\n:::\n\n```{.r .cell-code}\nhotpo(4.5)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in hotpo(4.5): round(x) == x is not TRUE\n```\n:::\n:::\n\n\n\n## One through ten\n- Use a data frame of numbers 1 through 10 again:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x = 1:10) %>% mutate(y = map_int(x, \\(x) hotpo(x)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Until I get to 1 (if I ever do) {.smaller}\n- If I start from a number, find `hotpo` of it, then find `hotpo` of that,\nand keep going, what happens?\n- If I get to 4, 2, 1, 4, 2, 1 I’ll repeat for ever, so let’s stop when we get\nto 1:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq <- function(x) {\n ans <- x\n while (x != 1) {\n x <- hotpo(x)\n ans <- c(ans, x)\n }\n ans\n}\n```\n:::\n\n\n- Strategy: keep looping “while `x` is not 1”.\n- Each new `x`: add to the end of `ans`. When I hit 1, I break\nout of the `while` and return the whole `ans`. \n\n## Trying it 1/2\n- Start at 6:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6 3 10 5 16 8 4 2 1\n```\n:::\n:::\n\n\n## Trying it 2/2 \n\n- Start at 27:\n\n\n::: {.cell}\n\n:::\n\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(27)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 27 82 41 124 62 31 94 47 142 71 214\n [12] 107 322 161 484 242 121 364 182 91 274 137\n [23] 412 206 103 310 155 466 233 700 350 175 526\n [34] 263 790 395 1186 593 1780 890 445 1336 668 334\n [45] 167 502 251 754 377 1132 566 283 850 425 1276\n [56] 638 319 958 479 1438 719 2158 1079 3238 1619 4858\n [67] 2429 7288 3644 1822 911 2734 1367 4102 2051 6154 3077\n [78] 9232 4616 2308 1154 577 1732 866 433 1300 650 325\n [89] 976 488 244 122 61 184 92 46 23 70 35\n[100] 106 53 160 80 40 20 10 5 16 8 4\n[111] 2 1\n```\n:::\n:::\n\n\\normalsize\n\n\n::: {.cell}\n\n:::\n\n\n\n## Which starting points have the longest sequences?\n- The `length` of the vector returned from `hotpo_seq` says how long it\ntook to get to 1.\n- Out of the starting points 1 to 100, which one has the longest\nsequence?\n\n## Top 10 longest sequences\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:100) %>%\n mutate(seq_length = map_int(\n start, \\(start) length(hotpo_seq(start)))) %>%\n slice_max(seq_length, n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- 27 is an unusually low starting point to have such a long sequence.\n\n## What happens if we save the entire sequence? \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n- Each entry in `sequence` is itself a vector. `sequence` is a\n“list-column”.\n\n## Using the whole sequence to find its length and its max \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start))) %>%\n mutate(\n seq_length = map_int(sequence, \\(sequence) length(sequence)),\n seq_max = map_int(sequence, \\(sequence) max(sequence))\n )\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n## Does it work with `rowwise`?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start=1:7) %>% \n rowwise() %>% \n mutate(sequence = list(hotpo_seq(start))) %>% \n mutate(seq_length = length(sequence)) %>% \n mutate(seq_max = max(sequence))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nIt does.\n\n## Final thoughts on this\n- Called the **Collatz conjecture**.\n- Nobody knows whether the sequence always gets to 1.\n- Nobody has found an $n$ for which it doesn’t.\n- A [tree](https://www.jasondavies.com/collatz-graph/).\n", "supporting": [ "functions_files" ], diff --git a/_freeze/functions/execute-results/tex.json b/_freeze/functions/execute-results/tex.json index 5dd70c5..b3b17fd 100644 --- a/_freeze/functions/execute-results/tex.json +++ b/_freeze/functions/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "85a3c227148dfefa4d5d86603ab0f099", + "hash": "892a465f0a928b45c1aa747892880c5f", "result": { - "markdown": "---\ntitle: \"Functions\"\n---\n\n\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n\n## Don’t repeat yourself\n- See this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\na <- 50\nb <- 11\nd <- 3\nas <- sqrt(a - 1)\nas\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nbs <- sqrt(b - 1)\nbs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nds <- sqrt(d - 1)\nds\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n\n## What's the problem?\n\n- Same calculation done three different times, by copying, pasting and\nediting.\n- Dangerous: what if you forget to change something after you pasted?\n\n- Programming principle: \"don't repeat yourself\".\n- Hadley Wickham: don't copy-paste more than twice.\n- Instead: *write a function*. \n\n## Anatomy of function\n- Header line with function name and input value(s).\n- Body with calculation of values to output/return.\n- Return value: the output from function.\nIn our case:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n ans <- sqrt(x - 1)\n return(ans)\n}\n```\n:::\n\n\n\nor more simply (\"the R way\", better style)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n sqrt(x - 1)\n}\n```\n:::\n\n\n\nIf last line of function calculates value without saving it, that value is\nreturned.\n\n## About the input; testing\n- The input to a function can be called anything. Here we called it `x`.\nThis is the name used inside the function.\n- The function is a “machine” for calculating square-root-minus-1. It\ndoesn’t do anything until you call it:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(50)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(11)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n- It works!\n\n## Vectorization 1/2 \n\n- We conceived our function to work on numbers:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(3.25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.5\n```\n:::\n:::\n\n\n\n- but it actually works on vectors too, as a free bonus of R:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(c(50, 11, 3))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.000000 3.162278 1.414214\n```\n:::\n:::\n\n\n\n- or... (over)\n\n## Vectorization 2/2 \n\n- or even data frames:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- tibble(x = 1:2, y = 3:4)\nsqrt_minus_1(d)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x y\n1 0 1.414214\n2 1 1.732051\n```\n:::\n:::\n\n\n\n## More than one input\n\n- Allow the value to be subtracted, before taking square root, to be\ninput to function as well, thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d) {\n sqrt(x - d)\n}\n```\n:::\n\n\n\n- Call the function with the x and d inputs in the right order:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n\n\n- or give the inputs names, in which case they can be in *any order*:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(d = 2, x = 51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n\n\n## Defaults 1/2\n- Many R functions have values that you can change if you want to,\nbut usually you don’t want to, for example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- c(3, 4, 5, NA, 6, 7)\nmean(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NA\n```\n:::\n\n```{.r .cell-code}\nmean(x, na.rm = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5\n```\n:::\n:::\n\n\n\n- By default, the mean of data with a missing value is missing, but if\nyou specify `na.rm=TRUE`, the missing values are removed before the mean\nis calculated.\n\n- That is, `na.rm` has a default value of `FALSE`: that’s what it will be unless\nyou change it.\n\n## Defaults 2/2\n- In our function, set a default value for d like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n sqrt(x - d)\n}\n```\n:::\n\n\n\n- If you specify a value for d, it will be used. If you don't, 1 will be used instead: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_value(51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.071068\n```\n:::\n:::\n\n\n\n## Catching errors before they happen\n- What happened here?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-stderr}\n```\nWarning in sqrt(x - d): NaNs produced\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NaN\n```\n:::\n:::\n\n\n\n- Message not helpful. Actually, function tried to take square root of\nnegative number.\n- In fact, not even error, just warning.\n- Check that the square root will be OK first. Here’s how:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n stopifnot(x - d >= 0)\n sqrt(x - d)\n}\n```\n:::\n\n\n\n## What happens with `stopifnot`\n- This should be good, and is:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(8, 6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n- This should fail, and see how it does:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in sqrt_minus_value(6, 8): x - d >= 0 is not TRUE\n```\n:::\n:::\n\n\n\n- Where the function fails, we get informative error, but if everything\ngood, the `stopifnot` does nothing.\n- `stopifnot` contains one or more logical conditions, and all of them\nhave to be true for function to work. So put in everything that you\nwant to be true.\n\n## Using R’s built-ins\n- When you write a function, you can use anything built-in to R, or\neven any functions that you defined before.\n- For example, if you will be calculating a lot of regression-line slopes,\nyou don’t have to do this from scratch: you can use R’s regression\ncalculations, like this:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df <- tibble(x = 1:4, y = c(10, 11, 10, 14))\n# my_df\nmy_df.1 <- lm(y ~ x, data = my_df)\n# summary(my_df.1)\ntidy(my_df.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 8.5 1.88 4.53 0.0455\n2 x 1.1 0.686 1.60 0.250 \n```\n:::\n:::\n\n\n\n## Pulling out just the slope\n\nUse `pluck`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(my_df.1) %>% pluck(\"estimate\", 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n\n\n## Making this into a function\n- First step: make sure you have it working without a function (we do)\n- Inputs: two, an `x` and a `y`.\n- Output: just the slope, a number. Thus:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy) {\n y.1 <- lm(yy ~ xx)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n\n- Check using our data from before: correct:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n\n## Passing things on\n- `lm` has a lot of options, with defaults, that we might want to change.\nInstead of intercepting all the possibilities and passing them on, we\ncan do this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy, ...) {\n y.1 <- lm(yy ~ xx, ...)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n\n- The `...` in the header line means “accept any other input”, and the\n`...` in the `lm` line means “pass anything other than `x` and `y` straight\non to `lm`”.\n\n\n## Using `...`\n- One of the things `lm` will accept is a vector called `subset` containing\nthe list of observations to include in the regression.\n- So we should be able to do this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y, subset = 3:4))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 4\n```\n:::\n:::\n\n\n\n\n- Just uses the last two observations in `x` and `y`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df %>% slice(3:4)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n x y\n \n1 3 10\n2 4 14\n```\n:::\n:::\n\n\n\n\n- so the slope should\nbe $(14 − 10)/(4 − 3) = 4$ and is.\n\n\n\n## Running a function for each of several inputs\n- Suppose we have a data frame containing several different `x`’s to use\nin regressions, along with the `y` we had before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(x1 = 1:4, x2 = c(8, 7, 6, 5), x3 = c(2, 4, 6, 9)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 3\n x1 x2 x3\n \n1 1 8 2\n2 2 7 4\n3 3 6 6\n4 4 5 9\n```\n:::\n:::\n\n\n\n- Want to use these as different x’s for a regression with `y` from `my_df` as the\nresponse, and collect together the three different slopes.\n- Python-like way: a `for` loop.\n- R-like way: `map_dbl`: less coding, but more thinking.\n\n## The loop way\n- “Pull out” column `i` of data frame `d` as `d %>% pull(i)`.\n- Create empty vector `slopes` to store the slopes.\n- Looping variable `i` goes from 1 to 3 (3 columns, thus 3 slopes):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslopes <- numeric(3)\nfor (i in 1:3) {\n d %>% pull(i) -> xx\n slopes[i] <- slope(xx, my_df$y)\n}\nslopes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1000000 -1.1000000 0.5140187\n```\n:::\n:::\n\n\n\n- Check this by doing the three `lm`s, one at a time.\n\n## The `map_dbl` way\n- In words: for each of these (columns of `d`), run function (`slope`) with inputs\n\"it\" and `y`), and collect together the answers.\n- Since slope returns a decimal number (a `dbl`), appropriate\nfunction-running function is `map_dbl`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) slope(d, my_df$y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n 1.1000000 -1.1000000 0.5140187 \n```\n:::\n:::\n\n\n\n- Same as loop, with a lot less coding.\n\n## Square roots\n\n- “Find the square roots of each of the numbers 1 through 10”:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 1:10\nmap_dbl(x, \\(x) sqrt(x))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427\n [9] 3.000000 3.162278\n```\n:::\n:::\n\n\n\n## Summarizing all columns of a data frame, two ways\n\n- use my `d` from above: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) mean(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n2.50 6.50 5.25 \n```\n:::\n\n```{.r .cell-code}\nd %>% summarize(across(everything(), \\(x) mean(x)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 3\n x1 x2 x3\n \n1 2.5 6.5 5.25\n```\n:::\n:::\n\n\n\nThe mean of each column, with the columns labelled. \n\n## What if summary returns more than one thing?\n- For example, finding quartiles:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nquartiles <- function(x) {\n quantile(x, c(0.25, 0.75))\n}\nquartiles(1:5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n25% 75% \n 2 4 \n```\n:::\n:::\n\n\n\n- When function returns more than one thing, `map` (or `map_df`) instead\nof `map_dbl`.\n\n## map results\n- Try:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$x1\n 25% 75% \n1.75 3.25 \n\n$x2\n 25% 75% \n5.75 7.25 \n\n$x3\n 25% 75% \n3.50 6.75 \n```\n:::\n:::\n\n\n\n- A list. \n\n## Or\n\n- Better: pretend output from quartiles is one-column data\nframe: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_df(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 2\n `25%` `75%`\n \n1 1.75 3.25\n2 5.75 7.25\n3 3.5 6.75\n```\n:::\n:::\n\n\n\n## Or even\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% map_df(\\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 2\n `25%` `75%`\n \n1 1.75 3.25\n2 5.75 7.25\n3 3.5 6.75\n```\n:::\n:::\n\n\n\n## Comments\n\n- This works because the implicit first thing in map is (the columns of) the\ndata frame that came out of the previous step.\n- These are 1st and 3rd quartiles of each column of `d`, according to R’s\ndefault definition (see help for `quantile`).\n\n## `Map` in data frames with `mutate`\n- `map` can also be used within data frames to calculate new columns.\nLet’s do the square roots of 1 through 10 again:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- tibble(x = 1:10)\nd %>% mutate(root = map_dbl(x, \\(x) sqrt(x)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n x root\n \n 1 1 1 \n 2 2 1.41\n 3 3 1.73\n 4 4 2 \n 5 5 2.24\n 6 6 2.45\n 7 7 2.65\n 8 8 2.83\n 9 9 3 \n10 10 3.16\n```\n:::\n:::\n\n\n\n## Write a function first and then map it\n- If the “for each” part is simple, go ahead and use `map_`-whatever.\n- If not, write a function to do the complicated thing first.\n- Example: “half or triple plus one”: if the input is an even number,\nhalve it; if it is an odd number, multiply it by three and add one.\n- This is hard to do as a one-liner: first we have to figure out whether\nthe input is odd or even, and then we have to do the right thing with\nit.\n\n## Odd or even?\n\n\n- Odd or even? Work out the remainder when dividing by 2:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n6 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0\n```\n:::\n\n```{.r .cell-code}\n5 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1\n```\n:::\n:::\n\n\n\n- 5 has remainder 1 so it is odd.\n\n## Write the function\n- First test for integerness, then test for odd or even, and then do the appropriate calculation:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo <- function(x) {\n stopifnot(round(x) == x) # passes if input an integer\n remainder <- x %% 2\n if (remainder == 1) {\n ans <- 3 * x + 1\n }\n else {\n ans <- x %/% 2 # integer division\n }\n ans\n}\n```\n:::\n\n\n\n## Test it \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 10\n```\n:::\n\n```{.r .cell-code}\nhotpo(12)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6\n```\n:::\n\n```{.r .cell-code}\nhotpo(4.5)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in hotpo(4.5): round(x) == x is not TRUE\n```\n:::\n:::\n\n\n\n\n## One through ten\n- Use a data frame of numbers 1 through 10 again:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x = 1:10) %>% mutate(y = map_int(x, \\(x) hotpo(x)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n x y\n \n 1 1 4\n 2 2 1\n 3 3 10\n 4 4 2\n 5 5 16\n 6 6 3\n 7 7 22\n 8 8 4\n 9 9 28\n10 10 5\n```\n:::\n:::\n\n\n\n## Until I get to 1 (if I ever do) {.smaller}\n- If I start from a number, find `hotpo` of it, then find `hotpo` of that,\nand keep going, what happens?\n- If I get to 4, 2, 1, 4, 2, 1 I’ll repeat for ever, so let’s stop when we get\nto 1:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq <- function(x) {\n ans <- x\n while (x != 1) {\n x <- hotpo(x)\n ans <- c(ans, x)\n }\n ans\n}\n```\n:::\n\n\n\n- Strategy: keep looping “while `x` is not 1”.\n- Each new `x`: add to the end of `ans`. When I hit 1, I break\nout of the `while` and return the whole `ans`. \n\n## Trying it 1/2\n- Start at 6:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6 3 10 5 16 8 4 2 1\n```\n:::\n:::\n\n\n\n## Trying it 2/2 \n\n- Start at 27:\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(27)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 27 82 41 124 62 31 94 47 142 71 214\n [12] 107 322 161 484 242 121 364 182 91 274 137\n [23] 412 206 103 310 155 466 233 700 350 175 526\n [34] 263 790 395 1186 593 1780 890 445 1336 668 334\n [45] 167 502 251 754 377 1132 566 283 850 425 1276\n [56] 638 319 958 479 1438 719 2158 1079 3238 1619 4858\n [67] 2429 7288 3644 1822 911 2734 1367 4102 2051 6154 3077\n [78] 9232 4616 2308 1154 577 1732 866 433 1300 650 325\n [89] 976 488 244 122 61 184 92 46 23 70 35\n[100] 106 53 160 80 40 20 10 5 16 8 4\n[111] 2 1\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n## Which starting points have the longest sequences?\n- The `length` of the vector returned from `hotpo_seq` says how long it\ntook to get to 1.\n- Out of the starting points 1 to 100, which one has the longest\nsequence?\n\n## Top 10 longest sequences\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:100) %>%\n mutate(seq_length = map_int(\n start, \\(start) length(hotpo_seq(start)))) %>%\n slice_max(seq_length, n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n start seq_length\n \n 1 97 119\n 2 73 116\n 3 54 113\n 4 55 113\n 5 27 112\n 6 82 111\n 7 83 111\n 8 41 110\n 9 62 108\n10 63 108\n```\n:::\n:::\n\n\n\n- 27 is an unusually low starting point to have such a long sequence.\n\n## What happens if we save the entire sequence? \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n start sequence \n \n1 1 \n2 2 \n3 3 \n4 4 \n5 5 \n6 6 \n7 7 \n```\n:::\n:::\n\n\n\n\n- Each entry in `sequence` is itself a vector. `sequence` is a\n“list-column”.\n\n## Using the whole sequence to find its length and its max \n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start))) %>%\n mutate(\n seq_length = map_int(sequence, \\(sequence) length(sequence)),\n seq_max = map_int(sequence, \\(sequence) max(sequence))\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 4\n start sequence seq_length seq_max\n \n1 1 1 1\n2 2 2 2\n3 3 8 16\n4 4 3 4\n5 5 6 16\n6 6 9 16\n7 7 17 52\n```\n:::\n:::\n\n\n\n\n\n## Does it work with `rowwise`?\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start=1:7) %>% \n rowwise() %>% \n mutate(sequence = list(hotpo_seq(start))) %>% \n mutate(seq_length = length(sequence)) %>% \n mutate(seq_max = max(sequence))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 4\n# Rowwise: \n start sequence seq_length seq_max\n \n1 1 1 1\n2 2 2 2\n3 3 8 16\n4 4 3 4\n5 5 6 16\n6 6 9 16\n7 7 17 52\n```\n:::\n:::\n\n\n\nIt does.\n\n## Final thoughts on this\n- Called the **Collatz conjecture**.\n- Nobody knows whether the sequence always gets to 1.\n- Nobody has found an $n$ for which it doesn’t.\n- A [tree](https://www.jasondavies.com/collatz-graph/).\n", + "markdown": "---\ntitle: \"Functions\"\n---\n\n\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n# install.packages(\"vctrs\")\n```\n:::\n\n\n\n\n## Don’t repeat yourself\n- See this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\na <- 50\nb <- 11\nd <- 3\nas <- sqrt(a - 1)\nas\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nbs <- sqrt(b - 1)\nbs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nds <- sqrt(d - 1)\nds\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n\n## What's the problem?\n\n- Same calculation done three different times, by copying, pasting and\nediting.\n- Dangerous: what if you forget to change something after you pasted?\n\n- Programming principle: \"don't repeat yourself\".\n- Hadley Wickham: don't copy-paste more than twice.\n- Instead: *write a function*. \n\n## Anatomy of function\n- Header line with function name and input value(s).\n- Body with calculation of values to output/return.\n- Return value: the output from function.\nIn our case:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n ans <- sqrt(x - 1)\n return(ans)\n}\n```\n:::\n\n\n\nor more simply (\"the R way\", better style)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1 <- function(x) {\n sqrt(x - 1)\n}\n```\n:::\n\n\n\nIf last line of function calculates value without saving it, that value is\nreturned.\n\n## About the input; testing\n- The input to a function can be called anything. Here we called it `x`.\nThis is the name used inside the function.\n- The function is a “machine” for calculating square-root-minus-1. It\ndoesn’t do anything until you call it:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(50)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(11)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 3.162278\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nq <- 17\nsqrt_minus_1(q)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 4\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(\"text\")\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in x - 1: non-numeric argument to binary operator\n```\n:::\n:::\n\n\n\n\n- It works!\n\n## Vectorization 1/2 \n\n- We conceived our function to work on numbers:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(3.25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.5\n```\n:::\n:::\n\n\n\n- but it actually works on vectors too, as a free bonus of R:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_1(c(50, 11, 3))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.000000 3.162278 1.414214\n```\n:::\n:::\n\n\n\n- or... (over)\n\n## Vectorization 2/2 \n\n- or even data frames:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- data.frame(x = 1:2, y = 3:4)\nd\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x y\n1 1 3\n2 2 4\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_1(d)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x y\n1 0 1.414214\n2 1 1.732051\n```\n:::\n:::\n\n\n\n## More than one input\n\n- Allow the value to be subtracted, before taking square root, to be\ninput to function as well, thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d) {\n sqrt(x - d)\n}\n```\n:::\n\n\n\n- Call the function with the x and d inputs in the right order:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n\n\n- or give the inputs names, in which case they can be in *any order*:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(d = 2, x = 51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nlm(y ~ x, data = d)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = y ~ x, data = d)\n\nCoefficients:\n(Intercept) x \n 2 1 \n```\n:::\n:::\n\n\n\n\n## Defaults 1/2\n- Many R functions have values that you can change if you want to,\nbut usually you don’t want to, for example:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- c(3, 4, 5, NA, 6, 7)\nmean(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NA\n```\n:::\n\n```{.r .cell-code}\nmean(x, na.rm = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5\n```\n:::\n:::\n\n\n\n- By default, the mean of data with a missing value is missing, but if\nyou specify `na.rm=TRUE`, the missing values are removed before the mean\nis calculated.\n\n- That is, `na.rm` has a default value of `FALSE`: that’s what it will be unless\nyou change it.\n\n## Defaults 2/2\n- In our function, set a default value for d like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n sqrt(x - d)\n}\n```\n:::\n\n\n\n- If you specify a value for d, it will be used. If you don't, 1 will be used instead: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(51, 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7\n```\n:::\n\n```{.r .cell-code}\nsqrt_minus_value(51)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 7.071068\n```\n:::\n:::\n\n\n\n## Catching errors before they happen\n- What happened here?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-stderr}\n```\nWarning in sqrt(x - d): NaNs produced\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] NaN\n```\n:::\n:::\n\n\n\n- Message not helpful. Actually, function tried to take square root of\nnegative number.\n- In fact, not even error, just warning.\n- Check that the square root will be OK first. Here’s how:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value <- function(x, d = 1) {\n stopifnot(x - d >= 0)\n sqrt(x - d)\n}\n```\n:::\n\n\n\n## What happens with `stopifnot`\n- This should be good, and is:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(8, 6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.414214\n```\n:::\n:::\n\n\n\n- This should fail, and see how it does:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsqrt_minus_value(6, 8)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in sqrt_minus_value(6, 8): x - d >= 0 is not TRUE\n```\n:::\n:::\n\n\n\n- Where the function fails, we get informative error, but if everything\ngood, the `stopifnot` does nothing.\n- `stopifnot` contains one or more logical conditions, and all of them\nhave to be true for function to work. So put in everything that you\nwant to be true.\n\n## Using R’s built-ins\n- When you write a function, you can use anything built-in to R, or\neven any functions that you defined before.\n- For example, if you will be calculating a lot of regression-line slopes,\nyou don’t have to do this from scratch: you can use R’s regression\ncalculations, like this:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df <- data.frame(x = 1:4, y = c(10, 11, 10, 14))\nmy_df\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x y\n1 1 10\n2 2 11\n3 3 10\n4 4 14\n```\n:::\n\n```{.r .cell-code}\nmy_df.1 <- lm(y ~ x, data = my_df)\nsummary(my_df.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = y ~ x, data = my_df)\n\nResiduals:\n 1 2 3 4 \n 0.4 0.3 -1.8 1.1 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 8.5000 1.8775 4.527 0.0455 *\nx 1.1000 0.6856 1.605 0.2498 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.533 on 2 degrees of freedom\nMultiple R-squared: 0.5628,\tAdjusted R-squared: 0.3442 \nF-statistic: 2.574 on 1 and 2 DF, p-value: 0.2498\n```\n:::\n\n```{.r .cell-code}\ntidy(my_df.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 8.5 1.88 4.53 0.0455\n2 x 1.1 0.686 1.60 0.250 \n```\n:::\n:::\n\n\n\n## Pulling out just the slope\n\nUse `pluck`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(my_df.1) %>% pluck(\"estimate\", 2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n\n\n## Making this into a function\n- First step: make sure you have it working without a function (we do)\n- Inputs: two, an `x` and a `y`.\n- Output: just the slope, a number. Thus:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy) {\n y.1 <- lm(yy ~ xx)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n\n- Check using our data from before: correct:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1\n```\n:::\n:::\n\n\n\n## Passing things on\n- `lm` has a lot of options, with defaults, that we might want to change.\nInstead of intercepting all the possibilities and passing them on, we\ncan do this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslope <- function(xx, yy, ...) {\n y.1 <- lm(yy ~ xx, ...)\n tidy(y.1) %>% pluck(\"estimate\", 2)\n}\n```\n:::\n\n\n\n- The `...` in the header line means “accept any other input”, and the\n`...` in the `lm` line means “pass anything other than `x` and `y` straight\non to `lm`”.\n\n\n## Using `...`\n- One of the things `lm` will accept is a vector called `subset` containing\nthe list of observations to include in the regression.\n- So we should be able to do this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(my_df, slope(x, y, subset = 3:4))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 4\n```\n:::\n:::\n\n\n\n\n- Just uses the last two observations in `x` and `y`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_df %>% slice(3:4)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x y\n1 3 10\n2 4 14\n```\n:::\n:::\n\n\n\n\n- so the slope should\nbe $(14 − 10)/(4 − 3) = 4$ and is.\n\n\n\n## Running a function for each of several inputs\n- Suppose we have a data frame containing several different `x`’s to use\nin regressions, along with the `y` we had before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(x1 = 1:4, x2 = c(8, 7, 6, 5), x3 = c(2, 4, 6, 9)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 3\n x1 x2 x3\n \n1 1 8 2\n2 2 7 4\n3 3 6 6\n4 4 5 9\n```\n:::\n:::\n\n\n\n- Want to use these as different x’s for a regression with `y` from `my_df` as the\nresponse, and collect together the three different slopes.\n- Python-like way: a `for` loop.\n- R-like way: `map_dbl`: less coding, but more thinking.\n\n## The loop way\n- “Pull out” column `i` of data frame `d` as `d %>% pull(i)`.\n- Create empty vector `slopes` to store the slopes.\n- Looping variable `i` goes from 1 to 3 (3 columns, thus 3 slopes):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nslopes <- numeric(3)\nfor (i in 1:3) {\n d %>% pull(i) -> xx\n slopes[i] <- slope(xx, my_df$y)\n}\nslopes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1.1000000 -1.1000000 0.5140187\n```\n:::\n:::\n\n\n\n- Check this by doing the three `lm`s, one at a time.\n\n## The `map_dbl` way\n- In words: for each of these (columns of `d`), run function (`slope`) with inputs\n\"it\" and `y`), and collect together the answers.\n- Since slope returns a decimal number (a `dbl`), appropriate\nfunction-running function is `map_dbl`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) slope(d, my_df$y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n 1.1000000 -1.1000000 0.5140187 \n```\n:::\n:::\n\n\n\n- Same as loop, with a lot less coding.\n\n## Square roots\n\n- “Find the square roots of each of the numbers 1 through 10”:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 1:10\nmap_dbl(x, \\(x) sqrt(x))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427\n [9] 3.000000 3.162278\n```\n:::\n:::\n\n\n\n## Summarizing all columns of a data frame, two ways\n\n- use my `d` from above: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_dbl(d, \\(d) mean(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n x1 x2 x3 \n2.50 6.50 5.25 \n```\n:::\n\n```{.r .cell-code}\nd %>% summarize(across(everything(), \\(x) mean(x)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 3\n x1 x2 x3\n \n1 2.5 6.5 5.25\n```\n:::\n:::\n\n\n\nThe mean of each column, with the columns labelled. \n\n## What if summary returns more than one thing?\n- For example, finding quartiles:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nquartiles <- function(x) {\n quantile(x, c(0.25, 0.75))\n}\nquartiles(1:5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n25% 75% \n 2 4 \n```\n:::\n:::\n\n\n\n- When function returns more than one thing, `map` (or `map_df`) instead\nof `map_dbl`.\n\n## map results\n- Try:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$x1\n 25% 75% \n1.75 3.25 \n\n$x2\n 25% 75% \n5.75 7.25 \n\n$x3\n 25% 75% \n3.50 6.75 \n```\n:::\n:::\n\n\n\n- A list. \n\n## Or\n\n- Better: pretend output from quartiles is one-column data\nframe: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmap_df(d, \\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 2\n `25%` `75%`\n \n1 1.75 3.25\n2 5.75 7.25\n3 3.5 6.75\n```\n:::\n:::\n\n\n\n## Or even\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% map_df(\\(d) quartiles(d))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 2\n `25%` `75%`\n \n1 1.75 3.25\n2 5.75 7.25\n3 3.5 6.75\n```\n:::\n:::\n\n\n\n## Comments\n\n- This works because the implicit first thing in map is (the columns of) the\ndata frame that came out of the previous step.\n- These are 1st and 3rd quartiles of each column of `d`, according to R’s\ndefault definition (see help for `quantile`).\n\n## `Map` in data frames with `mutate`\n- `map` can also be used within data frames to calculate new columns.\nLet’s do the square roots of 1 through 10 again:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd <- tibble(x = 1:10)\nd %>% mutate(root = map_dbl(x, \\(x) sqrt(x)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n x root\n \n 1 1 1 \n 2 2 1.41\n 3 3 1.73\n 4 4 2 \n 5 5 2.24\n 6 6 2.45\n 7 7 2.65\n 8 8 2.83\n 9 9 3 \n10 10 3.16\n```\n:::\n:::\n\n\n\n## Write a function first and then map it\n- If the “for each” part is simple, go ahead and use `map_`-whatever.\n- If not, write a function to do the complicated thing first.\n- Example: “half or triple plus one”: if the input is an even number,\nhalve it; if it is an odd number, multiply it by three and add one.\n- This is hard to do as a one-liner: first we have to figure out whether\nthe input is odd or even, and then we have to do the right thing with\nit.\n\n## Odd or even?\n\n\n- Odd or even? Work out the remainder when dividing by 2:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n6 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0\n```\n:::\n\n```{.r .cell-code}\n5 %% 2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 1\n```\n:::\n:::\n\n\n\n- 5 has remainder 1 so it is odd.\n\n## Write the function\n- First test for integerness, then test for odd or even, and then do the appropriate calculation:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo <- function(x) {\n stopifnot(round(x) == x) # passes if input an integer\n remainder <- x %% 2\n if (remainder == 1) {\n ans <- 3 * x + 1\n }\n else {\n ans <- x %/% 2 # integer division\n }\n ans\n}\n```\n:::\n\n\n\n## Test it \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 10\n```\n:::\n\n```{.r .cell-code}\nhotpo(12)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6\n```\n:::\n\n```{.r .cell-code}\nhotpo(4.5)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in hotpo(4.5): round(x) == x is not TRUE\n```\n:::\n:::\n\n\n\n\n## One through ten\n- Use a data frame of numbers 1 through 10 again:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x = 1:10) %>% mutate(y = map_int(x, \\(x) hotpo(x)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n x y\n \n 1 1 4\n 2 2 1\n 3 3 10\n 4 4 2\n 5 5 16\n 6 6 3\n 7 7 22\n 8 8 4\n 9 9 28\n10 10 5\n```\n:::\n:::\n\n\n\n## Until I get to 1 (if I ever do) {.smaller}\n- If I start from a number, find `hotpo` of it, then find `hotpo` of that,\nand keep going, what happens?\n- If I get to 4, 2, 1, 4, 2, 1 I’ll repeat for ever, so let’s stop when we get\nto 1:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq <- function(x) {\n ans <- x\n while (x != 1) {\n x <- hotpo(x)\n ans <- c(ans, x)\n }\n ans\n}\n```\n:::\n\n\n\n- Strategy: keep looping “while `x` is not 1”.\n- Each new `x`: add to the end of `ans`. When I hit 1, I break\nout of the `while` and return the whole `ans`. \n\n## Trying it 1/2\n- Start at 6:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(6)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 6 3 10 5 16 8 4 2 1\n```\n:::\n:::\n\n\n\n## Trying it 2/2 \n\n- Start at 27:\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nhotpo_seq(27)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 27 82 41 124 62 31 94 47 142 71 214\n [12] 107 322 161 484 242 121 364 182 91 274 137\n [23] 412 206 103 310 155 466 233 700 350 175 526\n [34] 263 790 395 1186 593 1780 890 445 1336 668 334\n [45] 167 502 251 754 377 1132 566 283 850 425 1276\n [56] 638 319 958 479 1438 719 2158 1079 3238 1619 4858\n [67] 2429 7288 3644 1822 911 2734 1367 4102 2051 6154 3077\n [78] 9232 4616 2308 1154 577 1732 866 433 1300 650 325\n [89] 976 488 244 122 61 184 92 46 23 70 35\n[100] 106 53 160 80 40 20 10 5 16 8 4\n[111] 2 1\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n## Which starting points have the longest sequences?\n- The `length` of the vector returned from `hotpo_seq` says how long it\ntook to get to 1.\n- Out of the starting points 1 to 100, which one has the longest\nsequence?\n\n## Top 10 longest sequences\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:100) %>%\n mutate(seq_length = map_int(\n start, \\(start) length(hotpo_seq(start)))) %>%\n slice_max(seq_length, n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n start seq_length\n \n 1 97 119\n 2 73 116\n 3 54 113\n 4 55 113\n 5 27 112\n 6 82 111\n 7 83 111\n 8 41 110\n 9 62 108\n10 63 108\n```\n:::\n:::\n\n\n\n- 27 is an unusually low starting point to have such a long sequence.\n\n## What happens if we save the entire sequence? \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n start sequence \n \n1 1 \n2 2 \n3 3 \n4 4 \n5 5 \n6 6 \n7 7 \n```\n:::\n:::\n\n\n\n\n- Each entry in `sequence` is itself a vector. `sequence` is a\n“list-column”.\n\n## Using the whole sequence to find its length and its max \n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start = 1:7) %>%\n mutate(sequence = map(start, \\(start) hotpo_seq(start))) %>%\n mutate(\n seq_length = map_int(sequence, \\(sequence) length(sequence)),\n seq_max = map_int(sequence, \\(sequence) max(sequence))\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 4\n start sequence seq_length seq_max\n \n1 1 1 1\n2 2 2 2\n3 3 8 16\n4 4 3 4\n5 5 6 16\n6 6 9 16\n7 7 17 52\n```\n:::\n:::\n\n\n\n\n\n## Does it work with `rowwise`?\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(start=1:7) %>% \n rowwise() %>% \n mutate(sequence = list(hotpo_seq(start))) %>% \n mutate(seq_length = length(sequence)) %>% \n mutate(seq_max = max(sequence))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 4\n# Rowwise: \n start sequence seq_length seq_max\n \n1 1 1 1\n2 2 2 2\n3 3 8 16\n4 4 3 4\n5 5 6 16\n6 6 9 16\n7 7 17 52\n```\n:::\n:::\n\n\n\nIt does.\n\n## Final thoughts on this\n- Called the **Collatz conjecture**.\n- Nobody knows whether the sequence always gets to 1.\n- Nobody has found an $n$ for which it doesn’t.\n- A [tree](https://www.jasondavies.com/collatz-graph/).\n", "supporting": [ "functions_files" ], diff --git a/_freeze/inference_3/execute-results/html.json b/_freeze/inference_3/execute-results/html.json index 19b7121..57a6f39 100644 --- a/_freeze/inference_3/execute-results/html.json +++ b/_freeze/inference_3/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "93b6f03c25f7a63a663ef0ea3df560d9", + "hash": "5b0e5124fb7a28fbddd5ac626d27473a", "result": { - "markdown": "---\ntitle: \"The sign test\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n`smmr` is new. See later how to install it.\n\n## Duality between confidence intervals and hypothesis tests\n\n- Tests and CIs really do the same thing, if you look at them the\n right way. They are both telling you something about a parameter,\n and they use same things about data.\n- To illustrate, some data (two groups):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/duality.txt\"\ntwogroups <- read_delim(my_url,\" \")\n```\n:::\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntwogroups\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## 95% CI (default)\n\nfor difference in means, group 1 minus group 2:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n## 90% CI\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups, conf.level = 0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n90 percent confidence interval:\n -5.010308 -0.323025\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n## Too highHypothesis test\n\nNull is that difference in means is zero:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, mu=0, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n## Comparing results\n\nRecall null here is $H_0 : \\mu_1 - \\mu_2 = 0$. P-value 0.0668.\n\n- 95% CI from $-5.6$ to $0.2$, contains $0$.\n- 90% CI from $-5.0$ to $-0.3$, does not contain $0$.\n- At $\\alpha = 0.05$, would not reject $H_0$ since P-value $> 0.05$.\n- At $\\alpha = 0.10$, *would* reject $H_0$ since P-value $< 0.10$.\n\n## Test and CI\n\nNot just coincidence. Let $C = 100(1 - \\alpha)$, so C% gives\ncorresponding CI to level-$\\alpha$ test. Then following always true.\n(Symbol $\\iff$ means \"if and only if\".)\n\n| Test decision | | Confidence interval |\n|:--------------------------|:----------------:|:--------------------------|\n| Reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI does not contain $H_0$ value |\n| Do not reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI contains $H_0$ value |\n\nIdea: \"Plausible\" parameter value inside CI, not rejected; \"Implausible\"\nparameter value outside CI, rejected.\n\n## The value of this\n\n- If you have a test procedure but no corresponding CI:\n- you make a CI by including all the parameter values that would not\n be rejected by your test.\n- Use:\n - $\\alpha = 0.01$ for a 99% CI,\n - $\\alpha = 0.05$ for a 95% CI,\n - $\\alpha = 0.10$ for a 90% CI, and so on.\n\n## Testing for non-normal data\n\n- The IRS (\"Internal Revenue Service\") is the US authority that deals\n with taxes (like Revenue Canada).\n- One of their forms is supposed to take no more than 160 minutes to\n complete. A citizen's organization claims that it takes people\n longer than that on average.\n- Sample of 30 people; time to complete form recorded.\n- Read in data, and do $t$-test of $H_0 : \\mu = 160$ vs.\n $H_a : \\mu > 160$.\n- For reading in, there is only one column, so can pretend it is\n delimited by anything.\n\n## Read in data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/irs.txt\"\nirs <- read_csv(my_url)\nirs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Test whether mean is 160 or greater\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(irs, t.test(Time, mu = 160, \n alternative = \"greater\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne Sample t-test\n\ndata: Time\nt = 1.8244, df = 29, p-value = 0.03921\nalternative hypothesis: true mean is greater than 160\n95 percent confidence interval:\n 162.8305 Inf\nsample estimates:\nmean of x \n 201.2333 \n```\n:::\n:::\n\n\nReject null; mean (for all people to complete form) greater than 160.\n\n## But, look at a graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(irs, aes(x = Time)) + geom_histogram(bins = 6)\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-revealjs/inference-3-R-8-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Skewed to right.\n- Should look at *median*, not mean.\n\n## The sign test\n\n- But how to test whether the median is greater than 160?\n- Idea: if the median really is 160 ($H_0$ true), the sampled values\n from the population are equally likely to be above or below 160.\n- If the population median is greater than 160, there will be a lot of\n sample values greater than 160, not so many less. Idea: test\n statistic is number of sample values greater than hypothesized\n median.\n\n## Getting a P-value for sign test 1/3\n\n- How to decide whether \"unusually many\" sample values are greater\n than 160? Need a sampling distribution.\n- If $H_0$ true, pop. median is 160, then each sample value\n independently equally likely to be above or below 160.\n- So number of observed values above 160 has binomial distribution\n with $n = 30$ (number of data values) and $p = 0.5$ (160 is\n hypothesized to be *median*).\n\n## Getting P-value for sign test 2/3\n\n- Count values above/below 160:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% count(Time > 160)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- 17 above, 13 below. How unusual is that? Need a *binomial table*.\n\n## Getting P-value for sign test 3/3\n\n- R function `dbinom` gives the probability of eg. exactly 17\n successes in a binomial with $n = 30$ and $p = 0.5$:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndbinom(17, 30, 0.5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.1115351\n```\n:::\n:::\n\n\n- but we want probability of 17 *or more*, so get all of those, find\n probability of each, and add them up:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x=17:30) %>% \n mutate(prob=dbinom(x, 30, 0.5)) %>% \n summarize(total=sum(prob))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Using my package `smmr`\n\n- I wrote a package `smmr` to do the sign test (and some other\n things). Installation is a bit fiddly:\n - Install devtools (once) with\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"devtools\")\n```\n:::\n\n\n- then install `smmr` using `devtools` (once):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(devtools)\ninstall_github(\"nxskok/smmr\")\n```\n:::\n\n\n- Then load it:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(smmr)\n```\n:::\n\n\n## `smmr` for sign test\n\n- `smmr`'s function `sign_test` needs three inputs: a data frame, a\n column and a null median:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsign_test(irs, Time, 160)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$above_below\nbelow above \n 13 17 \n\n$p_values\n alternative p_value\n1 lower 0.8192027\n2 upper 0.2923324\n3 two-sided 0.5846647\n```\n:::\n:::\n\n\n## Comments (1/3)\n\n- Testing whether population median *greater than* 160, so want\n *upper-tail* P-value 0.2923. Same as before.\n- Also get table of values above and below; this too as we got.\n\n## Comments (2/3)\n\n- P-values are:\n\n| Test | P-value |\n|:-----|--------:|\n| $t$ | 0.0392 |\n| Sign | 0.2923 |\n\n- These are very different: we reject a mean of 160 (in favour of the\n mean being bigger), but clearly *fail* to reject a median of 160 in\n favour of a bigger one.\n- Why is that? Obtain mean and median:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% summarize(mean_time = mean(Time), \n median_time = median(Time))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments (3/3) {.smaller}\n\n- The mean is pulled a long way up by the right skew, and is a fair\n bit bigger than 160.\n- The median is quite close to 160.\n- We ought to be trusting the sign test and not the t-test here\n (median and not mean), and therefore there is no evidence that the\n \"typical\" time to complete the form is longer than 160 minutes.\n- Having said that, there are clearly some people who take a lot\n longer than 160 minutes to complete the form, and the IRS could\n focus on simplifying its form for these people.\n- In this example, looking at any kind of average is not really\n helpful; a better question might be \"do an unacceptably large\n fraction of people take longer than (say) 300 minutes to complete\n the form?\": that is, thinking about worst-case rather than\n average-case.\n\n## Confidence interval for the median\n\n- The sign test does not naturally come with a confidence interval for\n the median.\n- So we use the \"duality\" between test and confidence interval to say:\n the (95%) confidence interval for the median contains exactly those\n values of the null median that would not be rejected by the\n two-sided sign test (at $\\alpha = 0.05$).\n\n## For our data\n\n- The procedure is to try some values for the null median and see\n which ones are inside and which outside our CI.\n- smmr has pval_sign that gets just the 2-sided P-value:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(160, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.5846647\n```\n:::\n:::\n\n\n- Try a couple of null medians:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(200, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.3615946\n```\n:::\n\n```{.r .cell-code}\npval_sign(300, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.001430906\n```\n:::\n:::\n\n\n- So 200 inside the 95% CI and 300 outside.\n\n## Doing a whole bunch\n\n- Choose our null medians first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(null_median=seq(100,300,20)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## ... and then\n\n\"for each null median, run the function `pval_sign` for that null median\nand get the P-value\":\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Make it easier for ourselves\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time)) %>% \n mutate(in_out = ifelse(p_value > 0.05, \"inside\", \"outside\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## confidence interval for median?\n\n- 95% CI to this accuracy from 120 to 200.\n- Can get it more accurately by looking more closely in intervals from\n 100 to 120, and from 200 to 220.\n\n## A more efficient way: bisection\n\n- Know that top end of CI between 200 and 220:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- 200 \nhi <- 220\n```\n:::\n\n\n- Try the value halfway between: is it inside or outside?\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210\n```\n:::\n\n```{.r .cell-code}\npval_sign(try,irs,Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.09873715\n```\n:::\n:::\n\n\n- Inside, so upper end is between 210 and 220. Repeat (over):\n\n## ... bisection continued\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- try\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 215\n```\n:::\n\n```{.r .cell-code}\npval_sign(try, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.06142835\n```\n:::\n:::\n\n\n- 215 is inside too, so upper end between 215 and 220.\n- Continue until have as accurate a result as you want.\n\n## Bisection automatically\n\n- A loop, but not a `for` since we don't know how many times we're\n going around. Keep going `while` a condition is true:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo = 200\nhi = 220\nwhile (hi - lo > 1) {\n try = (hi + lo) / 2\n ptry = pval_sign(try, irs, Time)\n print(c(try, ptry))\n if (ptry <= 0.05)\n hi = try\n else\n lo = try\n}\n```\n:::\n\n\n## The output from this loop\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210.00000000 0.09873715\n[1] 215.00000000 0.06142835\n[1] 217.50000000 0.04277395\n[1] 216.25000000 0.04277395\n[1] 215.62500000 0.04277395\n```\n:::\n:::\n\n\n- 215 inside, 215.625 outside. Upper end of interval to this accuracy\n is 215.\n\n## Using smmr\n\n- `smmr` has function `ci_median` that does this (by default 95% CI):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 119.0065 214.9955\n```\n:::\n:::\n\n\n- Uses a more accurate bisection than we did.\n- Or get, say, 90% CI for median:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time, conf.level=0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.0031 208.9960\n```\n:::\n:::\n\n\n- 90% CI is shorter, as it should be.\n\n## Bootstrap\n\n- but, was the sample size (30) big enough to overcome the skewness?\n- Bootstrap, again:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(my_sample = list(sample(irs$Time, replace = TRUE))) %>% \n mutate(my_mean = mean(my_sample)) %>% \n ggplot(aes(x=my_mean)) + geom_histogram(bins=10) -> g\n```\n:::\n\n\n## The sampling distribution\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-revealjs/inference-3-R-30-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- A little skewed to right, but not nearly as much as I was expecting.\n- The $t$-test for the mean might actually be OK for these data, *if\n the mean is what you want*.\n- In actual data, mean and median very different; we chose to make\n inference about the median.\n- Thus for us it was right to use the sign test.\n", + "markdown": "---\ntitle: \"The sign test\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n`smmr` is new. See later how to install it.\n\n## Duality between confidence intervals and hypothesis tests\n\n- Tests and CIs really do the same thing, if you look at them the\n right way. They are both telling you something about a parameter,\n and they use same things about data.\n- To illustrate, some data (two groups):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/duality.txt\"\ntwogroups <- read_delim(my_url,\" \")\n```\n:::\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntwogroups\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## 95% CI (default)\n\nfor difference in means, group 1 minus group 2:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n## 90% CI\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups, conf.level = 0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n90 percent confidence interval:\n -5.010308 -0.323025\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n## Too highHypothesis test\n\nNull is that difference in means is zero:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, mu=0, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n## Comparing results\n\nRecall null here is $H_0 : \\mu_1 - \\mu_2 = 0$. P-value 0.0668.\n\n- 95% CI from $-5.6$ to $0.2$, contains $0$.\n- 90% CI from $-5.0$ to $-0.3$, does not contain $0$.\n- At $\\alpha = 0.05$, would not reject $H_0$ since P-value $> 0.05$.\n- At $\\alpha = 0.10$, *would* reject $H_0$ since P-value $< 0.10$.\n\n## Test and CI\n\nNot just coincidence. Let $C = 100(1 - \\alpha)$, so C% gives\ncorresponding CI to level-$\\alpha$ test. Then following always true.\n(Symbol $\\iff$ means \"if and only if\".)\n\n| Test decision | | Confidence interval |\n|:----------------------------|:-------------:|:----------------------------|\n| Reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI does not contain $H_0$ value |\n| Do not reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI contains $H_0$ value |\n\nIdea: \"Plausible\" parameter value inside CI, not rejected; \"Implausible\"\nparameter value outside CI, rejected.\n\n## The value of this\n\n- If you have a test procedure but no corresponding CI:\n- you make a CI by including all the parameter values that would not\n be rejected by your test.\n- Use:\n - $\\alpha = 0.01$ for a 99% CI,\n - $\\alpha = 0.05$ for a 95% CI,\n - $\\alpha = 0.10$ for a 90% CI, and so on.\n\n## Testing for non-normal data\n\n- The IRS (\"Internal Revenue Service\") is the US authority that deals\n with taxes (like Revenue Canada).\n- One of their forms is supposed to take no more than 160 minutes to\n complete. A citizen's organization claims that it takes people\n longer than that on average.\n- Sample of 30 people; time to complete form recorded.\n- Read in data, and do $t$-test of $H_0 : \\mu = 160$ vs.\n $H_a : \\mu > 160$.\n- For reading in, there is only one column, so can pretend it is\n delimited by anything.\n\n## Read in data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/irs.txt\"\nirs <- read_csv(my_url)\nirs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Test whether mean is 160 or greater\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(irs, t.test(Time, mu = 160, \n alternative = \"greater\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne Sample t-test\n\ndata: Time\nt = 1.8244, df = 29, p-value = 0.03921\nalternative hypothesis: true mean is greater than 160\n95 percent confidence interval:\n 162.8305 Inf\nsample estimates:\nmean of x \n 201.2333 \n```\n:::\n:::\n\n\nReject null; mean (for all people to complete form) greater than 160.\n\n## But, look at a graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(irs, aes(x = Time)) + geom_histogram(bins = 6)\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-revealjs/inference-3-R-8-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Skewed to right.\n- Should look at *median*, not mean.\n\n## The sign test\n\n- But how to test whether the median is greater than 160?\n- Idea: if the median really is 160 ($H_0$ true), the sampled values\n from the population are equally likely to be above or below 160.\n- If the population median is greater than 160, there will be a lot of\n sample values greater than 160, not so many less. Idea: test\n statistic is number of sample values greater than hypothesized\n median.\n\n## Getting a P-value for sign test 1/3\n\n- How to decide whether \"unusually many\" sample values are greater\n than 160? Need a sampling distribution.\n- If $H_0$ true, pop. median is 160, then each sample value\n independently equally likely to be above or below 160.\n- So number of observed values above 160 has binomial distribution\n with $n = 30$ (number of data values) and $p = 0.5$ (160 is\n hypothesized to be *median*).\n\n## Getting P-value for sign test 2/3\n\n- Count values above/below 160:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% count(Time > 160)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- 17 above, 13 below. How unusual is that? Need a *binomial table*.\n\n## Getting P-value for sign test 3/3\n\n- R function `dbinom` gives the probability of eg. exactly 17\n successes in a binomial with $n = 30$ and $p = 0.5$:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndbinom(17, 30, 0.5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.1115351\n```\n:::\n:::\n\n\n- but we want probability of 17 *or more*, so get all of those, find\n probability of each, and add them up:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x=17:30) %>% \n mutate(prob=dbinom(x, 30, 0.5)) %>% \n summarize(total=sum(prob))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Using my package `smmr`\n\n- I wrote a package `smmr` to do the sign test (and some other\n things). Installation is a bit fiddly:\n - Install devtools (once) with\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"devtools\")\n```\n:::\n\n\n- then install `smmr` using `devtools` (once):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(devtools)\ninstall_github(\"nxskok/smmr\")\n```\n:::\n\n\n- Then load it:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(smmr)\n```\n:::\n\n\n## `smmr` for sign test\n\n- `smmr`'s function `sign_test` needs three inputs: a data frame, a\n column and a null median:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsign_test(irs, Time, 160)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$above_below\nbelow above \n 13 17 \n\n$p_values\n alternative p_value\n1 lower 0.8192027\n2 upper 0.2923324\n3 two-sided 0.5846647\n```\n:::\n:::\n\n\n## Comments (1/3)\n\n- Testing whether population median *greater than* 160, so want\n *upper-tail* P-value 0.2923. Same as before.\n- Also get table of values above and below; this too as we got.\n\n## Comments (2/3)\n\n- P-values are:\n\n| Test | P-value |\n|:-----|--------:|\n| $t$ | 0.0392 |\n| Sign | 0.2923 |\n\n- These are very different: we reject a mean of 160 (in favour of the\n mean being bigger), but clearly *fail* to reject a median of 160 in\n favour of a bigger one.\n- Why is that? Obtain mean and median:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% summarize(mean_time = mean(Time), \n median_time = median(Time))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments (3/3) {.smaller}\n\n- The mean is pulled a long way up by the right skew, and is a fair\n bit bigger than 160.\n- The median is quite close to 160.\n- We ought to be trusting the sign test and not the t-test here\n (median and not mean), and therefore there is no evidence that the\n \"typical\" time to complete the form is longer than 160 minutes.\n- Having said that, there are clearly some people who take a lot\n longer than 160 minutes to complete the form, and the IRS could\n focus on simplifying its form for these people.\n- In this example, looking at any kind of average is not really\n helpful; a better question might be \"do an unacceptably large\n fraction of people take longer than (say) 300 minutes to complete\n the form?\": that is, thinking about worst-case rather than\n average-case.\n\n## Confidence interval for the median\n\n- The sign test does not naturally come with a confidence interval for\n the median.\n- So we use the \"duality\" between test and confidence interval to say:\n the (95%) confidence interval for the median contains exactly those\n values of the null median that would not be rejected by the\n two-sided sign test (at $\\alpha = 0.05$).\n\n## For our data\n\n- The procedure is to try some values for the null median and see\n which ones are inside and which outside our CI.\n- smmr has pval_sign that gets just the 2-sided P-value:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(160, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.5846647\n```\n:::\n:::\n\n\n- Try a couple of null medians:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(200, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.3615946\n```\n:::\n\n```{.r .cell-code}\npval_sign(300, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.001430906\n```\n:::\n:::\n\n\n- So 200 inside the 95% CI and 300 outside.\n\n## Doing a whole bunch\n\n- Choose our null medians first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(null_median=seq(100,300,20)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## ... and then\n\n\"for each null median, run the function `pval_sign` for that null median\nand get the P-value\":\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Make it easier for ourselves\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time)) %>% \n mutate(in_out = ifelse(p_value > 0.05, \"inside\", \"outside\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## confidence interval for median?\n\n- 95% CI to this accuracy from 120 to 200.\n- Can get it more accurately by looking more closely in intervals from\n 100 to 120, and from 200 to 220.\n\n## A more efficient way: bisection\n\n- Know that top end of CI between 200 and 220:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- 200 \nhi <- 220\n```\n:::\n\n\n- Try the value halfway between: is it inside or outside?\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210\n```\n:::\n\n```{.r .cell-code}\npval_sign(try,irs,Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.09873715\n```\n:::\n:::\n\n\n- Inside, so upper end is between 210 and 220. Repeat (over):\n\n## ... bisection continued\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- try\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 215\n```\n:::\n\n```{.r .cell-code}\npval_sign(try, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.06142835\n```\n:::\n:::\n\n\n- 215 is inside too, so upper end between 215 and 220.\n- Continue until have as accurate a result as you want.\n\n## Bisection automatically\n\n- A loop, but not a `for` since we don't know how many times we're\n going around. Keep going `while` a condition is true:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo = 200\nhi = 220\nwhile (hi - lo > 1) {\n try = (hi + lo) / 2\n ptry = pval_sign(try, irs, Time)\n print(c(try, ptry))\n if (ptry <= 0.05)\n hi = try\n else\n lo = try\n}\n```\n:::\n\n\n## The output from this loop\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210.00000000 0.09873715\n[1] 215.00000000 0.06142835\n[1] 217.50000000 0.04277395\n[1] 216.25000000 0.04277395\n[1] 215.62500000 0.04277395\n```\n:::\n:::\n\n\n- 215 inside, 215.625 outside. Upper end of interval to this accuracy\n is 215.\n\n## Using smmr\n\n- `smmr` has function `ci_median` that does this (by default 95% CI):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 119.0065 214.9955\n```\n:::\n:::\n\n\n- Uses a more accurate bisection than we did.\n- Or get, say, 90% CI for median:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time, conf.level=0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.0031 208.9960\n```\n:::\n:::\n\n\n- 90% CI is shorter, as it should be.\n\n## Bootstrap\n\n- but, was the sample size (30) big enough to overcome the skewness?\n- Bootstrap, again:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(my_sample = list(sample(irs$Time, replace = TRUE))) %>% \n mutate(my_mean = mean(my_sample)) %>% \n ggplot(aes(x=my_mean)) + geom_histogram(bins=10) -> g\n```\n:::\n\n\n## The sampling distribution\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-revealjs/inference-3-R-30-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- A little skewed to right, but not nearly as much as I was expecting.\n- The $t$-test for the mean might actually be OK for these data, *if\n the mean is what you want*.\n- In actual data, mean and median very different; we chose to make\n inference about the median.\n- Thus for us it was right to use the sign test.\n", "supporting": [ "inference_3_files/figure-revealjs" ], diff --git a/_freeze/inference_3/execute-results/tex.json b/_freeze/inference_3/execute-results/tex.json index 257c962..d85abd6 100644 --- a/_freeze/inference_3/execute-results/tex.json +++ b/_freeze/inference_3/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "93b6f03c25f7a63a663ef0ea3df560d9", + "hash": "5b0e5124fb7a28fbddd5ac626d27473a", "result": { - "markdown": "---\ntitle: \"The sign test\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n\n`smmr` is new. See later how to install it.\n\n## Duality between confidence intervals and hypothesis tests\n\n- Tests and CIs really do the same thing, if you look at them the\n right way. They are both telling you something about a parameter,\n and they use same things about data.\n- To illustrate, some data (two groups):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/duality.txt\"\ntwogroups <- read_delim(my_url,\" \")\n```\n:::\n\n\n\n## The data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntwogroups\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 15 x 2\n y group\n \n 1 10 1\n 2 11 1\n 3 11 1\n 4 13 1\n 5 13 1\n 6 14 1\n 7 14 1\n 8 15 1\n 9 16 1\n10 13 2\n11 13 2\n12 14 2\n13 17 2\n14 18 2\n15 19 2\n```\n:::\n:::\n\n\n\n## 95% CI (default)\n\nfor difference in means, group 1 minus group 2:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n\n## 90% CI\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups, conf.level = 0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n90 percent confidence interval:\n -5.010308 -0.323025\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n\n## Too highHypothesis test\n\nNull is that difference in means is zero:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, mu=0, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n\n## Comparing results\n\nRecall null here is $H_0 : \\mu_1 - \\mu_2 = 0$. P-value 0.0668.\n\n- 95% CI from $-5.6$ to $0.2$, contains $0$.\n- 90% CI from $-5.0$ to $-0.3$, does not contain $0$.\n- At $\\alpha = 0.05$, would not reject $H_0$ since P-value $> 0.05$.\n- At $\\alpha = 0.10$, *would* reject $H_0$ since P-value $< 0.10$.\n\n## Test and CI\n\nNot just coincidence. Let $C = 100(1 - \\alpha)$, so C% gives\ncorresponding CI to level-$\\alpha$ test. Then following always true.\n(Symbol $\\iff$ means \"if and only if\".)\n\n| Test decision | | Confidence interval |\n|:--------------------------|:----------------:|:--------------------------|\n| Reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI does not contain $H_0$ value |\n| Do not reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI contains $H_0$ value |\n\nIdea: \"Plausible\" parameter value inside CI, not rejected; \"Implausible\"\nparameter value outside CI, rejected.\n\n## The value of this\n\n- If you have a test procedure but no corresponding CI:\n- you make a CI by including all the parameter values that would not\n be rejected by your test.\n- Use:\n - $\\alpha = 0.01$ for a 99% CI,\n - $\\alpha = 0.05$ for a 95% CI,\n - $\\alpha = 0.10$ for a 90% CI, and so on.\n\n## Testing for non-normal data\n\n- The IRS (\"Internal Revenue Service\") is the US authority that deals\n with taxes (like Revenue Canada).\n- One of their forms is supposed to take no more than 160 minutes to\n complete. A citizen's organization claims that it takes people\n longer than that on average.\n- Sample of 30 people; time to complete form recorded.\n- Read in data, and do $t$-test of $H_0 : \\mu = 160$ vs.\n $H_a : \\mu > 160$.\n- For reading in, there is only one column, so can pretend it is\n delimited by anything.\n\n## Read in data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/irs.txt\"\nirs <- read_csv(my_url)\nirs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 30 x 1\n Time\n \n 1 91\n 2 64\n 3 243\n 4 167\n 5 123\n 6 65\n 7 71\n 8 204\n 9 110\n10 178\n# ... with 20 more rows\n```\n:::\n:::\n\n\n\n## Test whether mean is 160 or greater\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(irs, t.test(Time, mu = 160, \n alternative = \"greater\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne Sample t-test\n\ndata: Time\nt = 1.8244, df = 29, p-value = 0.03921\nalternative hypothesis: true mean is greater than 160\n95 percent confidence interval:\n 162.8305 Inf\nsample estimates:\nmean of x \n 201.2333 \n```\n:::\n:::\n\n\n\nReject null; mean (for all people to complete form) greater than 160.\n\n## But, look at a graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(irs, aes(x = Time)) + geom_histogram(bins = 6)\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-beamer/inference-3-R-8-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Skewed to right.\n- Should look at *median*, not mean.\n\n## The sign test\n\n- But how to test whether the median is greater than 160?\n- Idea: if the median really is 160 ($H_0$ true), the sampled values\n from the population are equally likely to be above or below 160.\n- If the population median is greater than 160, there will be a lot of\n sample values greater than 160, not so many less. Idea: test\n statistic is number of sample values greater than hypothesized\n median.\n\n## Getting a P-value for sign test 1/3\n\n- How to decide whether \"unusually many\" sample values are greater\n than 160? Need a sampling distribution.\n- If $H_0$ true, pop. median is 160, then each sample value\n independently equally likely to be above or below 160.\n- So number of observed values above 160 has binomial distribution\n with $n = 30$ (number of data values) and $p = 0.5$ (160 is\n hypothesized to be *median*).\n\n## Getting P-value for sign test 2/3\n\n- Count values above/below 160:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% count(Time > 160)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n `Time > 160` n\n \n1 FALSE 13\n2 TRUE 17\n```\n:::\n:::\n\n\n\n- 17 above, 13 below. How unusual is that? Need a *binomial table*.\n\n## Getting P-value for sign test 3/3\n\n- R function `dbinom` gives the probability of eg. exactly 17\n successes in a binomial with $n = 30$ and $p = 0.5$:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndbinom(17, 30, 0.5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.1115351\n```\n:::\n:::\n\n\n\n- but we want probability of 17 *or more*, so get all of those, find\n probability of each, and add them up:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x=17:30) %>% \n mutate(prob=dbinom(x, 30, 0.5)) %>% \n summarize(total=sum(prob))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n total\n \n1 0.292\n```\n:::\n:::\n\n\n\n## Using my package `smmr`\n\n- I wrote a package `smmr` to do the sign test (and some other\n things). Installation is a bit fiddly:\n - Install devtools (once) with\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"devtools\")\n```\n:::\n\n\n\n- then install `smmr` using `devtools` (once):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(devtools)\ninstall_github(\"nxskok/smmr\")\n```\n:::\n\n\n\n- Then load it:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(smmr)\n```\n:::\n\n\n\n## `smmr` for sign test\n\n- `smmr`'s function `sign_test` needs three inputs: a data frame, a\n column and a null median:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsign_test(irs, Time, 160)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$above_below\nbelow above \n 13 17 \n\n$p_values\n alternative p_value\n1 lower 0.8192027\n2 upper 0.2923324\n3 two-sided 0.5846647\n```\n:::\n:::\n\n\n\n## Comments (1/3)\n\n- Testing whether population median *greater than* 160, so want\n *upper-tail* P-value 0.2923. Same as before.\n- Also get table of values above and below; this too as we got.\n\n## Comments (2/3)\n\n- P-values are:\n\n| Test | P-value |\n|:-----|--------:|\n| $t$ | 0.0392 |\n| Sign | 0.2923 |\n\n- These are very different: we reject a mean of 160 (in favour of the\n mean being bigger), but clearly *fail* to reject a median of 160 in\n favour of a bigger one.\n- Why is that? Obtain mean and median:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% summarize(mean_time = mean(Time), \n median_time = median(Time))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 2\n mean_time median_time\n \n1 201. 172.\n```\n:::\n:::\n\n\n\n## Comments (3/3) {.smaller}\n\n- The mean is pulled a long way up by the right skew, and is a fair\n bit bigger than 160.\n- The median is quite close to 160.\n- We ought to be trusting the sign test and not the t-test here\n (median and not mean), and therefore there is no evidence that the\n \"typical\" time to complete the form is longer than 160 minutes.\n- Having said that, there are clearly some people who take a lot\n longer than 160 minutes to complete the form, and the IRS could\n focus on simplifying its form for these people.\n- In this example, looking at any kind of average is not really\n helpful; a better question might be \"do an unacceptably large\n fraction of people take longer than (say) 300 minutes to complete\n the form?\": that is, thinking about worst-case rather than\n average-case.\n\n## Confidence interval for the median\n\n- The sign test does not naturally come with a confidence interval for\n the median.\n- So we use the \"duality\" between test and confidence interval to say:\n the (95%) confidence interval for the median contains exactly those\n values of the null median that would not be rejected by the\n two-sided sign test (at $\\alpha = 0.05$).\n\n## For our data\n\n- The procedure is to try some values for the null median and see\n which ones are inside and which outside our CI.\n- smmr has pval_sign that gets just the 2-sided P-value:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(160, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.5846647\n```\n:::\n:::\n\n\n\n- Try a couple of null medians:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(200, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.3615946\n```\n:::\n\n```{.r .cell-code}\npval_sign(300, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.001430906\n```\n:::\n:::\n\n\n\n- So 200 inside the 95% CI and 300 outside.\n\n## Doing a whole bunch\n\n- Choose our null medians first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(null_median=seq(100,300,20)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 1\n null_median\n \n 1 100\n 2 120\n 3 140\n 4 160\n 5 180\n 6 200\n 7 220\n 8 240\n 9 260\n10 280\n11 300\n```\n:::\n:::\n\n\n\n## ... and then\n\n\"for each null median, run the function `pval_sign` for that null median\nand get the P-value\":\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 2\n# Rowwise: \n null_median p_value\n \n 1 100 0.000325\n 2 120 0.0987 \n 3 140 0.200 \n 4 160 0.585 \n 5 180 0.856 \n 6 200 0.362 \n 7 220 0.0428 \n 8 240 0.0161 \n 9 260 0.00522 \n10 280 0.00143 \n11 300 0.00143 \n```\n:::\n:::\n\n\n\n## Make it easier for ourselves\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time)) %>% \n mutate(in_out = ifelse(p_value > 0.05, \"inside\", \"outside\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 3\n# Rowwise: \n null_median p_value in_out \n \n 1 100 0.000325 outside\n 2 120 0.0987 inside \n 3 140 0.200 inside \n 4 160 0.585 inside \n 5 180 0.856 inside \n 6 200 0.362 inside \n 7 220 0.0428 outside\n 8 240 0.0161 outside\n 9 260 0.00522 outside\n10 280 0.00143 outside\n11 300 0.00143 outside\n```\n:::\n:::\n\n\n\n## confidence interval for median?\n\n- 95% CI to this accuracy from 120 to 200.\n- Can get it more accurately by looking more closely in intervals from\n 100 to 120, and from 200 to 220.\n\n## A more efficient way: bisection\n\n- Know that top end of CI between 200 and 220:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- 200 \nhi <- 220\n```\n:::\n\n\n\n- Try the value halfway between: is it inside or outside?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210\n```\n:::\n\n```{.r .cell-code}\npval_sign(try,irs,Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.09873715\n```\n:::\n:::\n\n\n\n- Inside, so upper end is between 210 and 220. Repeat (over):\n\n## ... bisection continued\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- try\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 215\n```\n:::\n\n```{.r .cell-code}\npval_sign(try, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.06142835\n```\n:::\n:::\n\n\n\n- 215 is inside too, so upper end between 215 and 220.\n- Continue until have as accurate a result as you want.\n\n## Bisection automatically\n\n- A loop, but not a `for` since we don't know how many times we're\n going around. Keep going `while` a condition is true:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo = 200\nhi = 220\nwhile (hi - lo > 1) {\n try = (hi + lo) / 2\n ptry = pval_sign(try, irs, Time)\n print(c(try, ptry))\n if (ptry <= 0.05)\n hi = try\n else\n lo = try\n}\n```\n:::\n\n\n\n## The output from this loop\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210.00000000 0.09873715\n[1] 215.00000000 0.06142835\n[1] 217.50000000 0.04277395\n[1] 216.25000000 0.04277395\n[1] 215.62500000 0.04277395\n```\n:::\n:::\n\n\n\n- 215 inside, 215.625 outside. Upper end of interval to this accuracy\n is 215.\n\n## Using smmr\n\n- `smmr` has function `ci_median` that does this (by default 95% CI):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 119.0065 214.9955\n```\n:::\n:::\n\n\n\n- Uses a more accurate bisection than we did.\n- Or get, say, 90% CI for median:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time, conf.level=0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.0031 208.9960\n```\n:::\n:::\n\n\n\n- 90% CI is shorter, as it should be.\n\n## Bootstrap\n\n- but, was the sample size (30) big enough to overcome the skewness?\n- Bootstrap, again:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(my_sample = list(sample(irs$Time, replace = TRUE))) %>% \n mutate(my_mean = mean(my_sample)) %>% \n ggplot(aes(x=my_mean)) + geom_histogram(bins=10) -> g\n```\n:::\n\n\n\n## The sampling distribution\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-beamer/inference-3-R-30-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- A little skewed to right, but not nearly as much as I was expecting.\n- The $t$-test for the mean might actually be OK for these data, *if\n the mean is what you want*.\n- In actual data, mean and median very different; we chose to make\n inference about the median.\n- Thus for us it was right to use the sign test.\n", + "markdown": "---\ntitle: \"The sign test\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n\n`smmr` is new. See later how to install it.\n\n## Duality between confidence intervals and hypothesis tests\n\n- Tests and CIs really do the same thing, if you look at them the\n right way. They are both telling you something about a parameter,\n and they use same things about data.\n- To illustrate, some data (two groups):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/duality.txt\"\ntwogroups <- read_delim(my_url,\" \")\n```\n:::\n\n\n\n## The data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntwogroups\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 15 x 2\n y group\n \n 1 10 1\n 2 11 1\n 3 11 1\n 4 13 1\n 5 13 1\n 6 14 1\n 7 14 1\n 8 15 1\n 9 16 1\n10 13 2\n11 13 2\n12 14 2\n13 17 2\n14 18 2\n15 19 2\n```\n:::\n:::\n\n\n\n## 95% CI (default)\n\nfor difference in means, group 1 minus group 2:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n\n## 90% CI\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, data = twogroups, conf.level = 0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n90 percent confidence interval:\n -5.010308 -0.323025\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n\n## Too highHypothesis test\n\nNull is that difference in means is zero:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(y ~ group, mu=0, data = twogroups)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: y by group\nt = -2.0937, df = 8.7104, p-value = 0.0668\nalternative hypothesis: true difference in means between group 1 and group 2 is not equal to 0\n95 percent confidence interval:\n -5.5625675 0.2292342\nsample estimates:\nmean in group 1 mean in group 2 \n 13.00000 15.66667 \n```\n:::\n:::\n\n\n\n## Comparing results\n\nRecall null here is $H_0 : \\mu_1 - \\mu_2 = 0$. P-value 0.0668.\n\n- 95% CI from $-5.6$ to $0.2$, contains $0$.\n- 90% CI from $-5.0$ to $-0.3$, does not contain $0$.\n- At $\\alpha = 0.05$, would not reject $H_0$ since P-value $> 0.05$.\n- At $\\alpha = 0.10$, *would* reject $H_0$ since P-value $< 0.10$.\n\n## Test and CI\n\nNot just coincidence. Let $C = 100(1 - \\alpha)$, so C% gives\ncorresponding CI to level-$\\alpha$ test. Then following always true.\n(Symbol $\\iff$ means \"if and only if\".)\n\n| Test decision | | Confidence interval |\n|:----------------------------|:-------------:|:----------------------------|\n| Reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI does not contain $H_0$ value |\n| Do not reject $H_0$ at level $\\alpha$ | $\\iff$ | $C\\%$ CI contains $H_0$ value |\n\nIdea: \"Plausible\" parameter value inside CI, not rejected; \"Implausible\"\nparameter value outside CI, rejected.\n\n## The value of this\n\n- If you have a test procedure but no corresponding CI:\n- you make a CI by including all the parameter values that would not\n be rejected by your test.\n- Use:\n - $\\alpha = 0.01$ for a 99% CI,\n - $\\alpha = 0.05$ for a 95% CI,\n - $\\alpha = 0.10$ for a 90% CI, and so on.\n\n## Testing for non-normal data\n\n- The IRS (\"Internal Revenue Service\") is the US authority that deals\n with taxes (like Revenue Canada).\n- One of their forms is supposed to take no more than 160 minutes to\n complete. A citizen's organization claims that it takes people\n longer than that on average.\n- Sample of 30 people; time to complete form recorded.\n- Read in data, and do $t$-test of $H_0 : \\mu = 160$ vs.\n $H_a : \\mu > 160$.\n- For reading in, there is only one column, so can pretend it is\n delimited by anything.\n\n## Read in data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/irs.txt\"\nirs <- read_csv(my_url)\nirs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 30 x 1\n Time\n \n 1 91\n 2 64\n 3 243\n 4 167\n 5 123\n 6 65\n 7 71\n 8 204\n 9 110\n10 178\n# i 20 more rows\n```\n:::\n:::\n\n\n\n## Test whether mean is 160 or greater\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(irs, t.test(Time, mu = 160, \n alternative = \"greater\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne Sample t-test\n\ndata: Time\nt = 1.8244, df = 29, p-value = 0.03921\nalternative hypothesis: true mean is greater than 160\n95 percent confidence interval:\n 162.8305 Inf\nsample estimates:\nmean of x \n 201.2333 \n```\n:::\n:::\n\n\n\nReject null; mean (for all people to complete form) greater than 160.\n\n## But, look at a graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(irs, aes(x = Time)) + geom_histogram(bins = 6)\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-beamer/inference-3-R-8-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Skewed to right.\n- Should look at *median*, not mean.\n\n## The sign test\n\n- But how to test whether the median is greater than 160?\n- Idea: if the median really is 160 ($H_0$ true), the sampled values\n from the population are equally likely to be above or below 160.\n- If the population median is greater than 160, there will be a lot of\n sample values greater than 160, not so many less. Idea: test\n statistic is number of sample values greater than hypothesized\n median.\n\n## Getting a P-value for sign test 1/3\n\n- How to decide whether \"unusually many\" sample values are greater\n than 160? Need a sampling distribution.\n- If $H_0$ true, pop. median is 160, then each sample value\n independently equally likely to be above or below 160.\n- So number of observed values above 160 has binomial distribution\n with $n = 30$ (number of data values) and $p = 0.5$ (160 is\n hypothesized to be *median*).\n\n## Getting P-value for sign test 2/3\n\n- Count values above/below 160:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% count(Time > 160)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n `Time > 160` n\n \n1 FALSE 13\n2 TRUE 17\n```\n:::\n:::\n\n\n\n- 17 above, 13 below. How unusual is that? Need a *binomial table*.\n\n## Getting P-value for sign test 3/3\n\n- R function `dbinom` gives the probability of eg. exactly 17\n successes in a binomial with $n = 30$ and $p = 0.5$:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndbinom(17, 30, 0.5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.1115351\n```\n:::\n:::\n\n\n\n- but we want probability of 17 *or more*, so get all of those, find\n probability of each, and add them up:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(x=17:30) %>% \n mutate(prob=dbinom(x, 30, 0.5)) %>% \n summarize(total=sum(prob))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n total\n \n1 0.292\n```\n:::\n:::\n\n\n\n## Using my package `smmr`\n\n- I wrote a package `smmr` to do the sign test (and some other\n things). Installation is a bit fiddly:\n - Install devtools (once) with\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"devtools\")\n```\n:::\n\n\n\n- then install `smmr` using `devtools` (once):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(devtools)\ninstall_github(\"nxskok/smmr\")\n```\n:::\n\n\n\n- Then load it:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(smmr)\n```\n:::\n\n\n\n## `smmr` for sign test\n\n- `smmr`'s function `sign_test` needs three inputs: a data frame, a\n column and a null median:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsign_test(irs, Time, 160)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$above_below\nbelow above \n 13 17 \n\n$p_values\n alternative p_value\n1 lower 0.8192027\n2 upper 0.2923324\n3 two-sided 0.5846647\n```\n:::\n:::\n\n\n\n## Comments (1/3)\n\n- Testing whether population median *greater than* 160, so want\n *upper-tail* P-value 0.2923. Same as before.\n- Also get table of values above and below; this too as we got.\n\n## Comments (2/3)\n\n- P-values are:\n\n| Test | P-value |\n|:-----|--------:|\n| $t$ | 0.0392 |\n| Sign | 0.2923 |\n\n- These are very different: we reject a mean of 160 (in favour of the\n mean being bigger), but clearly *fail* to reject a median of 160 in\n favour of a bigger one.\n- Why is that? Obtain mean and median:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nirs %>% summarize(mean_time = mean(Time), \n median_time = median(Time))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 2\n mean_time median_time\n \n1 201. 172.\n```\n:::\n:::\n\n\n\n## Comments (3/3) {.smaller}\n\n- The mean is pulled a long way up by the right skew, and is a fair\n bit bigger than 160.\n- The median is quite close to 160.\n- We ought to be trusting the sign test and not the t-test here\n (median and not mean), and therefore there is no evidence that the\n \"typical\" time to complete the form is longer than 160 minutes.\n- Having said that, there are clearly some people who take a lot\n longer than 160 minutes to complete the form, and the IRS could\n focus on simplifying its form for these people.\n- In this example, looking at any kind of average is not really\n helpful; a better question might be \"do an unacceptably large\n fraction of people take longer than (say) 300 minutes to complete\n the form?\": that is, thinking about worst-case rather than\n average-case.\n\n## Confidence interval for the median\n\n- The sign test does not naturally come with a confidence interval for\n the median.\n- So we use the \"duality\" between test and confidence interval to say:\n the (95%) confidence interval for the median contains exactly those\n values of the null median that would not be rejected by the\n two-sided sign test (at $\\alpha = 0.05$).\n\n## For our data\n\n- The procedure is to try some values for the null median and see\n which ones are inside and which outside our CI.\n- smmr has pval_sign that gets just the 2-sided P-value:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(160, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.5846647\n```\n:::\n:::\n\n\n\n- Try a couple of null medians:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npval_sign(200, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.3615946\n```\n:::\n\n```{.r .cell-code}\npval_sign(300, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.001430906\n```\n:::\n:::\n\n\n\n- So 200 inside the 95% CI and 300 outside.\n\n## Doing a whole bunch\n\n- Choose our null medians first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(d <- tibble(null_median=seq(100,300,20)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 1\n null_median\n \n 1 100\n 2 120\n 3 140\n 4 160\n 5 180\n 6 200\n 7 220\n 8 240\n 9 260\n10 280\n11 300\n```\n:::\n:::\n\n\n\n## ... and then\n\n\"for each null median, run the function `pval_sign` for that null median\nand get the P-value\":\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 2\n# Rowwise: \n null_median p_value\n \n 1 100 0.000325\n 2 120 0.0987 \n 3 140 0.200 \n 4 160 0.585 \n 5 180 0.856 \n 6 200 0.362 \n 7 220 0.0428 \n 8 240 0.0161 \n 9 260 0.00522 \n10 280 0.00143 \n11 300 0.00143 \n```\n:::\n:::\n\n\n\n## Make it easier for ourselves\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% rowwise() %>% \n mutate(p_value = pval_sign(null_median, irs, Time)) %>% \n mutate(in_out = ifelse(p_value > 0.05, \"inside\", \"outside\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 3\n# Rowwise: \n null_median p_value in_out \n \n 1 100 0.000325 outside\n 2 120 0.0987 inside \n 3 140 0.200 inside \n 4 160 0.585 inside \n 5 180 0.856 inside \n 6 200 0.362 inside \n 7 220 0.0428 outside\n 8 240 0.0161 outside\n 9 260 0.00522 outside\n10 280 0.00143 outside\n11 300 0.00143 outside\n```\n:::\n:::\n\n\n\n## confidence interval for median?\n\n- 95% CI to this accuracy from 120 to 200.\n- Can get it more accurately by looking more closely in intervals from\n 100 to 120, and from 200 to 220.\n\n## A more efficient way: bisection\n\n- Know that top end of CI between 200 and 220:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- 200 \nhi <- 220\n```\n:::\n\n\n\n- Try the value halfway between: is it inside or outside?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210\n```\n:::\n\n```{.r .cell-code}\npval_sign(try,irs,Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.09873715\n```\n:::\n:::\n\n\n\n- Inside, so upper end is between 210 and 220. Repeat (over):\n\n## ... bisection continued\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo <- try\ntry <- (lo + hi) / 2\ntry\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 215\n```\n:::\n\n```{.r .cell-code}\npval_sign(try, irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.06142835\n```\n:::\n:::\n\n\n\n- 215 is inside too, so upper end between 215 and 220.\n- Continue until have as accurate a result as you want.\n\n## Bisection automatically\n\n- A loop, but not a `for` since we don't know how many times we're\n going around. Keep going `while` a condition is true:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlo = 200\nhi = 220\nwhile (hi - lo > 1) {\n try = (hi + lo) / 2\n ptry = pval_sign(try, irs, Time)\n print(c(try, ptry))\n if (ptry <= 0.05)\n hi = try\n else\n lo = try\n}\n```\n:::\n\n\n\n## The output from this loop\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n[1] 210.00000000 0.09873715\n[1] 215.00000000 0.06142835\n[1] 217.50000000 0.04277395\n[1] 216.25000000 0.04277395\n[1] 215.62500000 0.04277395\n```\n:::\n:::\n\n\n\n- 215 inside, 215.625 outside. Upper end of interval to this accuracy\n is 215.\n\n## Using smmr\n\n- `smmr` has function `ci_median` that does this (by default 95% CI):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 119.0065 214.9955\n```\n:::\n:::\n\n\n\n- Uses a more accurate bisection than we did.\n- Or get, say, 90% CI for median:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nci_median(irs, Time, conf.level=0.90)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.0031 208.9960\n```\n:::\n:::\n\n\n\n- 90% CI is shorter, as it should be.\n\n## Bootstrap\n\n- but, was the sample size (30) big enough to overcome the skewness?\n- Bootstrap, again:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntibble(sim = 1:1000) %>% \n rowwise() %>% \n mutate(my_sample = list(sample(irs$Time, replace = TRUE))) %>% \n mutate(my_mean = mean(my_sample)) %>% \n ggplot(aes(x=my_mean)) + geom_histogram(bins=10) -> g\n```\n:::\n\n\n\n## The sampling distribution\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](inference_3_files/figure-beamer/inference-3-R-30-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- A little skewed to right, but not nearly as much as I was expecting.\n- The $t$-test for the mean might actually be OK for these data, *if\n the mean is what you want*.\n- In actual data, mean and median very different; we chose to make\n inference about the median.\n- Thus for us it was right to use the sign test.\n", "supporting": [ "inference_3_files/figure-beamer" ], diff --git a/_freeze/inference_3/figure-beamer/inference-3-R-30-1.pdf b/_freeze/inference_3/figure-beamer/inference-3-R-30-1.pdf index 9b8c9c9..807ae6a 100644 Binary files a/_freeze/inference_3/figure-beamer/inference-3-R-30-1.pdf and b/_freeze/inference_3/figure-beamer/inference-3-R-30-1.pdf differ diff --git a/_freeze/inference_3/figure-beamer/inference-3-R-8-1.pdf b/_freeze/inference_3/figure-beamer/inference-3-R-8-1.pdf index ae35dc3..a684d49 100644 Binary files a/_freeze/inference_3/figure-beamer/inference-3-R-8-1.pdf and b/_freeze/inference_3/figure-beamer/inference-3-R-8-1.pdf differ diff --git a/_freeze/inference_5a/execute-results/html.json b/_freeze/inference_5a/execute-results/html.json index 5a5872c..a3acf06 100644 --- a/_freeze/inference_5a/execute-results/html.json +++ b/_freeze/inference_5a/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "156b7a8df976c1e3705c3cbcf5629678", + "hash": "8f9406f10bd58457eddca218b9249988", "result": { - "markdown": "---\ntitle: \"Mood's Median Test\"\n---\n\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n\n## Two-sample test: What to do if normality fails\n\n- If normality fails (for one or both of the groups), what do we do then?\n- Again, can compare medians: use the thought process of the sign test,\nwhich does not depend on normality and is not damaged by outliers.\n- A suitable test called Mood’s median test.\n- Before we get to that, a diversion.\n\n## The chi-squared test for independence\n\nSuppose we want to know whether people are in favour of having\ndaylight savings time all year round. We ask 20 males and 20 females\nwhether they each agree with having DST all year round (“yes”) or\nnot (“no”). Some randomly chosen data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/dst.txt\"\ndst <- read_delim(my_url,\" \")\ndst %>% slice_sample(n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## ... continued\n\nCount up individuals in each category combination, and arrange in\ncontingency table:\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(dst, table(gender, agree))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n agree\ngender no yes\n female 11 9\n male 3 17\n```\n:::\n:::\n\n\n- Most of the males say “yes”, but the females are about evenly split.\n- Looks like males more likely to say “yes”, ie. an association between\ngender and agreement.\n- Test an $H_0$ of “no association” (“independence”) vs. alternative that\nthere is really some association. \n- Done with `chisq.test`.\n\n## ...And finally\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab, correct=FALSE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 7.033, df = 1, p-value = 0.008002\n```\n:::\n:::\n\n\n- Reject null hypothesis of no association (P-value 0.008)\n- therefore there is a difference in rates of agreement between (all)\nmales and females (or that gender and agreement are associated).\n- This calculation gives same answers as you would get by hand. (Omitting `correct = FALSE` uses “Yates correction”.\n\n## Mood’s median test\n- Earlier: compare medians of two groups.\n- Sign test: count number of values above and below something\n(there, hypothesized median).\n- Mood’s median test:\n - Find \"grand median\" of all the data, regardless of group\n - Count data values in each group above/below grand\nmedian.\n - Make contingency table of group vs. above/below.\n - Test for association.\n- If group medians equal, each group should have about half its\nobservations above/below grand median. If not, one group will be\nmostly above grand median and other below.\n\n## Mood’s median test for reading data\n\n\n::: {.cell}\n\n:::\n\n\n\n- Find overall median score: \n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% summarize(med=median(score)) %>% pull(med) -> m\nm\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 47\n```\n:::\n:::\n\n\n- Make table of above/below vs. group:\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(kids, table(group, score > m))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n \ngroup FALSE TRUE\n c 15 8\n t 7 14\n```\n:::\n:::\n\n\n\n- Treatment group scores mostly above median, control group scores\nmostly below, as expected.\n\n## The test\n- Do chi-squared test:\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab,correct=F)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 4.4638, df = 1, p-value = 0.03462\n```\n:::\n:::\n\n\n\n- This test actually two-sided (tests for any association). \n- Here want to test that new reading method *better* (one-sided).\n- Most of treatment children above overall median, so\ndo 1-sided test by halving P-value to get 0.017. \n- This way too, children do better at learning to read using the new\nmethod.\n\n## Or by smmr\n- `median_test` does the whole thing:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(kids,score,group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 47\n\n$table\n above\ngroup above below\n c 8 15\n t 14 7\n\n$test\n what value\n1 statistic 4.46376812\n2 df 1.00000000\n3 P-value 0.03462105\n```\n:::\n:::\n\n\n- P-value again two-sided.\n\n## Comments\n- P-value 0.013 for (1-sided) t-test, 0.017 for (1-sided) Mood median\ntest.\n- Like the sign test, Mood’s median test doesn’t use the data very\nefficiently (only, is each value above or below grand median).\n- Thus, if we can justify doing *t*-test, we should do it. This is the case\nhere.\n- The *t*-test will usually give smaller P-value because it uses the data\nmore efficiently.\n- The time to use Mood’s median test is if we are definitely unhappy\nwith the normality assumption (and thus the t-test P-value is not to\nbe trusted).\n\n", + "markdown": "---\ntitle: \"Mood's Median Test\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n## Two-sample test: What to do if normality fails\n\n- If normality fails (for one or both of the groups), what do we do\n then?\n- Again, can compare medians: use the thought process of the sign\n test, which does not depend on normality and is not damaged by\n outliers.\n- A suitable test called Mood's median test.\n- Before we get to that, a diversion.\n\n## The chi-squared test for independence\n\nSuppose we want to know whether people are in favour of having daylight\nsavings time all year round. We ask 20 males and 20 females whether they\neach agree with having DST all year round (\"yes\") or not (\"no\"). Some\nrandomly chosen data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/dst.txt\"\ndst <- read_delim(my_url,\" \")\ndst %>% slice_sample(n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## ... continued\n\nCount up individuals in each category combination, and arrange in\ncontingency table:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(dst, table(gender, agree))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n agree\ngender no yes\n female 11 9\n male 3 17\n```\n:::\n:::\n\n\n- Most of the males say \"yes\", but the females are about evenly split.\n- Looks like males more likely to say \"yes\", ie. an association\n between gender and agreement.\n- Test an $H_0$ of \"no association\" (\"independence\") vs. alternative\n that there is really some association.\n- Done with `chisq.test`.\n\n## ...And finally\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab, correct=FALSE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 7.033, df = 1, p-value = 0.008002\n```\n:::\n:::\n\n\n- Reject null hypothesis of no association (P-value 0.008)\n- therefore there is a difference in rates of agreement between (all)\n males and females (or that gender and agreement are associated).\n- This calculation gives same answers as you would get by hand.\n (Omitting `correct = FALSE` uses \"Yates correction\".\n\n## Mood's median test\n\n- Earlier: compare medians of two groups.\n- Sign test: count number of values above and below something (there,\n hypothesized median).\n- Mood's median test:\n - Find \"grand median\" of all the data, regardless of group\n - Count data values in each group above/below grand median.\n - Make contingency table of group vs. above/below.\n - Test for association.\n- If group medians equal, each group should have about half its\n observations above/below grand median. If not, one group will be\n mostly above grand median and other below.\n\n## Mood's median test for reading data\n\n\n::: {.cell}\n\n:::\n\n\n- Find overall median score:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% summarize(med=median(score)) %>% pull(med) -> m\nm\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 47\n```\n:::\n:::\n\n\n- Make table of above/below vs. group:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(kids, table(group, score > m))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n \ngroup FALSE TRUE\n c 15 8\n t 7 14\n```\n:::\n:::\n\n\n- Treatment group scores mostly above median, control group scores\n mostly below, as expected.\n\n## The test\n\n- Do chi-squared test:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab, correct=F)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 4.4638, df = 1, p-value = 0.03462\n```\n:::\n:::\n\n\n- This test actually two-sided (tests for any association).\n- Here want to test that new reading method *better* (one-sided).\n- Most of treatment children above overall median, so do 1-sided test\n by halving P-value to get 0.017.\n- This way too, children do better at learning to read using the new\n method.\n\n## Or by smmr\n\n- `median_test` does the whole thing:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(kids,score,group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 47\n\n$table\n above\ngroup above below\n c 8 15\n t 14 7\n\n$test\n what value\n1 statistic 4.46376812\n2 df 1.00000000\n3 P-value 0.03462105\n```\n:::\n:::\n\n\n- P-value again two-sided.\n\n## Comments\n\n- P-value 0.013 for (1-sided) t-test, 0.017 for (1-sided) Mood median\n test.\n- Like the sign test, Mood's median test doesn't use the data very\n efficiently (only, is each value above or below grand median).\n- Thus, if we can justify doing *t*-test, we should do it. This is the\n case here.\n- The *t*-test will usually give smaller P-value because it uses the\n data more efficiently.\n- The time to use Mood's median test is if we are definitely unhappy\n with the normality assumption (and thus the t-test P-value is not to\n be trusted).\n", "supporting": [ "inference_5a_files" ], diff --git a/_freeze/inference_5a/execute-results/tex.json b/_freeze/inference_5a/execute-results/tex.json index 1866911..053c9d4 100644 --- a/_freeze/inference_5a/execute-results/tex.json +++ b/_freeze/inference_5a/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "156b7a8df976c1e3705c3cbcf5629678", + "hash": "8f9406f10bd58457eddca218b9249988", "result": { - "markdown": "---\ntitle: \"Mood's Median Test\"\n---\n\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n\n\n## Two-sample test: What to do if normality fails\n\n- If normality fails (for one or both of the groups), what do we do then?\n- Again, can compare medians: use the thought process of the sign test,\nwhich does not depend on normality and is not damaged by outliers.\n- A suitable test called Mood’s median test.\n- Before we get to that, a diversion.\n\n## The chi-squared test for independence\n\nSuppose we want to know whether people are in favour of having\ndaylight savings time all year round. We ask 20 males and 20 females\nwhether they each agree with having DST all year round (“yes”) or\nnot (“no”). Some randomly chosen data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/dst.txt\"\ndst <- read_delim(my_url,\" \")\ndst %>% slice_sample(n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n gender agree\n \n 1 female no \n 2 female no \n 3 male yes \n 4 male yes \n 5 male yes \n 6 female yes \n 7 male yes \n 8 female yes \n 9 female yes \n10 female yes \n```\n:::\n:::\n\n\n\n## ... continued\n\nCount up individuals in each category combination, and arrange in\ncontingency table:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(dst, table(gender, agree))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n agree\ngender no yes\n female 11 9\n male 3 17\n```\n:::\n:::\n\n\n\n- Most of the males say “yes”, but the females are about evenly split.\n- Looks like males more likely to say “yes”, ie. an association between\ngender and agreement.\n- Test an $H_0$ of “no association” (“independence”) vs. alternative that\nthere is really some association. \n- Done with `chisq.test`.\n\n## ...And finally\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab, correct=FALSE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 7.033, df = 1, p-value = 0.008002\n```\n:::\n:::\n\n\n\n- Reject null hypothesis of no association (P-value 0.008)\n- therefore there is a difference in rates of agreement between (all)\nmales and females (or that gender and agreement are associated).\n- This calculation gives same answers as you would get by hand. (Omitting `correct = FALSE` uses “Yates correction”.\n\n## Mood’s median test\n- Earlier: compare medians of two groups.\n- Sign test: count number of values above and below something\n(there, hypothesized median).\n- Mood’s median test:\n - Find \"grand median\" of all the data, regardless of group\n - Count data values in each group above/below grand\nmedian.\n - Make contingency table of group vs. above/below.\n - Test for association.\n- If group medians equal, each group should have about half its\nobservations above/below grand median. If not, one group will be\nmostly above grand median and other below.\n\n## Mood’s median test for reading data\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n- Find overall median score: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% summarize(med=median(score)) %>% pull(med) -> m\nm\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 47\n```\n:::\n:::\n\n\n\n- Make table of above/below vs. group:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(kids, table(group, score > m))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n \ngroup FALSE TRUE\n c 15 8\n t 7 14\n```\n:::\n:::\n\n\n\n\n- Treatment group scores mostly above median, control group scores\nmostly below, as expected.\n\n## The test\n- Do chi-squared test:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab,correct=F)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 4.4638, df = 1, p-value = 0.03462\n```\n:::\n:::\n\n\n\n\n- This test actually two-sided (tests for any association). \n- Here want to test that new reading method *better* (one-sided).\n- Most of treatment children above overall median, so\ndo 1-sided test by halving P-value to get 0.017. \n- This way too, children do better at learning to read using the new\nmethod.\n\n## Or by smmr\n- `median_test` does the whole thing:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(kids,score,group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 47\n\n$table\n above\ngroup above below\n c 8 15\n t 14 7\n\n$test\n what value\n1 statistic 4.46376812\n2 df 1.00000000\n3 P-value 0.03462105\n```\n:::\n:::\n\n\n\n- P-value again two-sided.\n\n## Comments\n- P-value 0.013 for (1-sided) t-test, 0.017 for (1-sided) Mood median\ntest.\n- Like the sign test, Mood’s median test doesn’t use the data very\nefficiently (only, is each value above or below grand median).\n- Thus, if we can justify doing *t*-test, we should do it. This is the case\nhere.\n- The *t*-test will usually give smaller P-value because it uses the data\nmore efficiently.\n- The time to use Mood’s median test is if we are definitely unhappy\nwith the normality assumption (and thus the t-test P-value is not to\nbe trusted).\n\n", + "markdown": "---\ntitle: \"Mood's Median Test\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\n```\n:::\n\n\n\n## Two-sample test: What to do if normality fails\n\n- If normality fails (for one or both of the groups), what do we do\n then?\n- Again, can compare medians: use the thought process of the sign\n test, which does not depend on normality and is not damaged by\n outliers.\n- A suitable test called Mood's median test.\n- Before we get to that, a diversion.\n\n## The chi-squared test for independence\n\nSuppose we want to know whether people are in favour of having daylight\nsavings time all year round. We ask 20 males and 20 females whether they\neach agree with having DST all year round (\"yes\") or not (\"no\"). Some\nrandomly chosen data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/dst.txt\"\ndst <- read_delim(my_url,\" \")\ndst %>% slice_sample(n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n gender agree\n \n 1 male yes \n 2 female no \n 3 male yes \n 4 female yes \n 5 male yes \n 6 female yes \n 7 male no \n 8 female no \n 9 female no \n10 female no \n```\n:::\n:::\n\n\n\n## ... continued\n\nCount up individuals in each category combination, and arrange in\ncontingency table:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(dst, table(gender, agree))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n agree\ngender no yes\n female 11 9\n male 3 17\n```\n:::\n:::\n\n\n\n- Most of the males say \"yes\", but the females are about evenly split.\n- Looks like males more likely to say \"yes\", ie. an association\n between gender and agreement.\n- Test an $H_0$ of \"no association\" (\"independence\") vs. alternative\n that there is really some association.\n- Done with `chisq.test`.\n\n## ...And finally\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab, correct=FALSE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 7.033, df = 1, p-value = 0.008002\n```\n:::\n:::\n\n\n\n- Reject null hypothesis of no association (P-value 0.008)\n- therefore there is a difference in rates of agreement between (all)\n males and females (or that gender and agreement are associated).\n- This calculation gives same answers as you would get by hand.\n (Omitting `correct = FALSE` uses \"Yates correction\".\n\n## Mood's median test\n\n- Earlier: compare medians of two groups.\n- Sign test: count number of values above and below something (there,\n hypothesized median).\n- Mood's median test:\n - Find \"grand median\" of all the data, regardless of group\n - Count data values in each group above/below grand median.\n - Make contingency table of group vs. above/below.\n - Test for association.\n- If group medians equal, each group should have about half its\n observations above/below grand median. If not, one group will be\n mostly above grand median and other below.\n\n## Mood's median test for reading data\n\n\n\n::: {.cell}\n\n:::\n\n\n\n- Find overall median score:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nkids %>% summarize(med=median(score)) %>% pull(med) -> m\nm\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 47\n```\n:::\n:::\n\n\n\n- Make table of above/below vs. group:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntab <- with(kids, table(group, score > m))\ntab\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n \ngroup FALSE TRUE\n c 15 8\n t 7 14\n```\n:::\n:::\n\n\n\n- Treatment group scores mostly above median, control group scores\n mostly below, as expected.\n\n## The test\n\n- Do chi-squared test:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nchisq.test(tab, correct=F)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tPearson's Chi-squared test\n\ndata: tab\nX-squared = 4.4638, df = 1, p-value = 0.03462\n```\n:::\n:::\n\n\n\n- This test actually two-sided (tests for any association).\n- Here want to test that new reading method *better* (one-sided).\n- Most of treatment children above overall median, so do 1-sided test\n by halving P-value to get 0.017.\n- This way too, children do better at learning to read using the new\n method.\n\n## Or by smmr\n\n- `median_test` does the whole thing:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(kids,score,group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 47\n\n$table\n above\ngroup above below\n c 8 15\n t 14 7\n\n$test\n what value\n1 statistic 4.46376812\n2 df 1.00000000\n3 P-value 0.03462105\n```\n:::\n:::\n\n\n\n- P-value again two-sided.\n\n## Comments\n\n- P-value 0.013 for (1-sided) t-test, 0.017 for (1-sided) Mood median\n test.\n- Like the sign test, Mood's median test doesn't use the data very\n efficiently (only, is each value above or below grand median).\n- Thus, if we can justify doing *t*-test, we should do it. This is the\n case here.\n- The *t*-test will usually give smaller P-value because it uses the\n data more efficiently.\n- The time to use Mood's median test is if we are definitely unhappy\n with the normality assumption (and thus the t-test P-value is not to\n be trusted).\n", "supporting": [ "inference_5a_files" ], diff --git a/_freeze/inference_5b/execute-results/html.json b/_freeze/inference_5b/execute-results/html.json index ce13c6a..87c3dac 100644 --- a/_freeze/inference_5b/execute-results/html.json +++ b/_freeze/inference_5b/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "3775a6bbfd15f9bddda4badaa71252aa", + "hash": "dcd78877625f55e2caecf5834490b26e", "result": { - "markdown": "---\ntitle: \"Analysis of variance\"\n---\n\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\nlibrary(PMCMRplus)\n```\n:::\n\n\n\n\n\n## Jumping rats\n- Link between exercise and healthy bones (many studies).\n- Exercise stresses bones and causes them to get stronger.\n- Study (Purdue): effect of jumping on bone density of growing rats.\n- 30 rats, randomly assigned to 1 of 3 treatments:\n - No jumping (control)\n - Low-jump treatment (30 cm)\n - High-jump treatment (60 cm)\n- 8 weeks, 10 jumps/day, 5 days/week.\n- Bone density of rats (mg/cm$^3$) measured at end.\n\n## Jumping rats 2/2\n\n- See whether larger amount of exercise (jumping) went with higher\nbone density.\n- Random assignment: rats in each group similar in all important ways.\n- So entitled to draw conclusions about cause and effect.\n\n## Reading the data\nValues separated by spaces:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/jumping.txt\"\nrats <- read_delim(my_url,\" \")\n```\n:::\n\n\n\\normalsize\n\n## The data (some random rows)\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% slice_sample(n=12)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Boxplots\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=group)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-11-1.png){width=960}\n:::\n:::\n\n\n## Or, arranging groups in data (logical) order\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=fct_inorder(group))) +\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-12-1.png){width=960}\n:::\n:::\n\n\n## Analysis of Variance\n- Comparing > 2 groups of independent observations (each rat only\ndoes one amount of jumping).\n- Standard procedure: analysis of variance (ANOVA).\n- Null hypothesis: all groups have same mean.\n- Alternative: “not all means the same”, at least one is different from\nothers.\n\n## Testing: ANOVA in R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group,data=rats)\nsummary(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \ngroup 2 7434 3717 7.978 0.0019 **\nResiduals 27 12579 466 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n- Usual ANOVA table, small P-value: significant result.\n- Conclude that the mean bone densities are not all equal.\n- Reject null, but not very useful finding.\n\n## Which groups are different from which?\n- ANOVA really only answers half our questions: it says “there are\ndifferences”, but doesn’t tell us which groups different.\n- One possibility (not the best): compare all possible pairs of groups,\nvia two-sample t.\n- First pick out each group:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% filter(group==\"Control\") -> controls\nrats %>% filter(group==\"Lowjump\") -> lows\nrats %>% filter(group==\"Highjump\") -> highs\n```\n:::\n\n\n## Control vs. low\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, lows$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and lows$density\nt = -1.0761, df = 16.191, p-value = 0.2977\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -33.83725 11.03725\nsample estimates:\nmean of x mean of y \n 601.1 612.5 \n```\n:::\n:::\n\n\nNo sig. difference here.\n\n## Control vs. high\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and highs$density\nt = -3.7155, df = 14.831, p-value = 0.002109\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -59.19139 -16.00861\nsample estimates:\nmean of x mean of y \n 601.1 638.7 \n```\n:::\n:::\n\n\nThese are different.\n\n## Low vs. high\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(lows$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: lows$density and highs$density\nt = -3.2523, df = 17.597, p-value = 0.004525\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -43.15242 -9.24758\nsample estimates:\nmean of x mean of y \n 612.5 638.7 \n```\n:::\n:::\n\n\nThese are different too.\n\n## But...\n- We just did 3 tests instead of 1.\n- So we have given ourselves 3 chances to reject $H_0:$ all means equal,\ninstead of 1.\n- Thus $\\alpha$ for this combined test is not 0.05.\n\n## John W. Tukey \n\n:::: {.columns}\n\n::: {.column width=\"40%\"}\n![](John_Tukey.jpg){width=400}\n:::\n\n::: {.column width=\"60%\"}\n\n- American statistician, 1915--2000\n- Big fan of exploratory data analysis\n- Popularized boxplot\n- Invented \"honestly significant differences\"\n- Invented jackknife estimation\n- Coined computing term \"bit\"\n- Co-inventor of Fast Fourier Transform\n \n:::\n\n::::\n\n## Honestly Significant Differences\n- Compare several groups with one test, telling you which groups differ\nfrom which.\n- Idea: if all population means equal, find distribution of highest sample\nmean minus lowest sample mean.\n- Any means unusually different compared to that declared significantly\ndifferent.\n\n## Tukey on rat data\n\n\n::: {.cell}\n\n:::\n\n\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group, data = rats)\nTukeyHSD(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = density ~ group, data = rats)\n\n$group\n diff lwr upr p adj\nHighjump-Control 37.6 13.66604 61.533957 0.0016388\nLowjump-Control 11.4 -12.53396 35.333957 0.4744032\nLowjump-Highjump -26.2 -50.13396 -2.266043 0.0297843\n```\n:::\n:::\n\n\\normalsize\n\n\n::: {.cell}\n\n:::\n\n\n- Again conclude that bone density for highjump group significantly higher\nthan for other two groups.\n\n## Why Tukey’s procedure better than all t-tests \nLook at P-values for the two tests:\n\n```\nComparison Tukey t-tests\n----------------------------------\nHighjump-Control 0.0016 0.0021\nLowjump-Control 0.4744 0.2977\nLowjump-Highjump 0.0298 0.0045\n```\n\n \n- Tukey P-values (mostly) higher.\n- Proper adjustment for doing three t-tests at once, not just one in\nisolation.\n\n## Checking assumptions\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats,aes(y = density, x = fct_inorder(group)))+\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-21-1.png){width=960}\n:::\n:::\n\n\nAssumptions:\n\n- Normally distributed data within each group\n- with equal group SDs.\n\n## Normal quantile plots by group\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(sample = density)) + stat_qq() + \n stat_qq_line() + facet_wrap( ~ group)\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-22-1.png){width=960}\n:::\n:::\n\n\n## The assumptions\n- Normally-distributed data within each group\n- Equal group SDs.\n- These are shaky here because:\n - control group has outliers\n - highjump group appears to have less spread than others.\n- Possible remedies (in general):\n - Transformation of response (usually works best when SD increases\nwith mean)\n - If normality OK but equal spreads not, can use Welch ANOVA.\n(Regular ANOVA like pooled t-test; Welch ANOVA like\nWelch-Satterthwaite t-test.)\n - Can also use Mood’s Median Test (see over). This works for any\nnumber of groups.\n\n## Mood’s median test here\n\n- Find median of all bone densities, regardless of group\n- Count up how many observations in each group above or below\noverall median\n- Test association between group and being above/below\noverall median, using chi-squared test.\n\n- Actually do this using `median_test`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(rats, density, group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 621.5\n\n$table\n above\ngroup above below\n Control 1 9\n Highjump 10 0\n Lowjump 4 6\n\n$test\n what value\n1 statistic 1.680000e+01\n2 df 2.000000e+00\n3 P-value 2.248673e-04\n```\n:::\n:::\n\n\n\n\n## Comments\n- No doubt that medians differ between groups (not all same). \n- This test is equivalent of $F$-test, not of Tukey. \n- To determine which groups differ from which, can compare all possible\npairs of groups via (2-sample) Mood’s median tests, then adjust\nP-values by multiplying by number of 2-sample Mood tests done (Bonferroni):\n\n\n::: {.cell}\n\n```{.r .cell-code}\npairwise_median_test(rats, density, group)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Now, lowjump-highjump difference no longer significant. \n\n## Welch ANOVA\n- For these data, Mood’s median test probably best because we doubt\nboth normality and equal spreads.\n- When normality OK but spreads differ, Welch ANOVA way to go.\n- Welch ANOVA done by `oneway.test` as shown (for illustration):\n\n\n::: {.cell}\n\n```{.r .cell-code}\noneway.test(density~group, data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne-way analysis of means (not assuming equal variances)\n\ndata: density and group\nF = 8.8164, num df = 2.000, denom df = 17.405, p-value = 0.002268\n```\n:::\n:::\n\n\n- P-value very similar, as expected.\n- Appropriate Tukey-equivalent here called Games-Howell.\n\n## Games-Howell\n\n- Lives in package `PMCMRplus`. Install\nfirst.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ngamesHowellTest(density~factor(group),data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Control Highjump\nHighjump 0.0056 - \nLowjump 0.5417 0.0120 \n```\n:::\n:::\n\n\n## Deciding which test to do\n\nFor two or more samples:\n\n![](testflow.png)\n\n", + "markdown": "---\ntitle: \"Analysis of variance\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\nlibrary(PMCMRplus)\n```\n:::\n\n\n## Jumping rats\n\n- Link between exercise and healthy bones (many studies).\n- Exercise stresses bones and causes them to get stronger.\n- Study (Purdue): effect of jumping on bone density of growing rats.\n- 30 rats, randomly assigned to 1 of 3 treatments:\n - No jumping (control)\n - Low-jump treatment (30 cm)\n - High-jump treatment (60 cm)\n- 8 weeks, 10 jumps/day, 5 days/week.\n- Bone density of rats (mg/cm$^3$) measured at end.\n\n## Jumping rats 2/2\n\n- See whether larger amount of exercise (jumping) went with higher\n bone density.\n- Random assignment: rats in each group similar in all important ways.\n- So entitled to draw conclusions about cause and effect.\n\n## Reading the data\n\nValues separated by spaces:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/jumping.txt\"\nrats <- read_delim(my_url,\" \")\n```\n:::\n\n\n## The data (some random rows)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% slice_sample(n=10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nrats\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Boxplots\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=group)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-11-1.png){width=960}\n:::\n:::\n\n\n## Or, arranging groups in data (logical) order\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=fct_inorder(group))) +\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-12-1.png){width=960}\n:::\n:::\n\n\n## Analysis of Variance\n\n- Comparing \\> 2 groups of independent observations (each rat only\n does one amount of jumping).\n- Standard procedure: analysis of variance (ANOVA).\n- Null hypothesis: all groups have same mean.\n- Alternative: \"not all means the same\", at least one is different\n from others.\n\n## Testing: ANOVA in R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group,data=rats)\nsummary(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \ngroup 2 7434 3717 7.978 0.0019 **\nResiduals 27 12579 466 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n- Usual ANOVA table, small P-value: significant result.\n- Conclude that the mean bone densities are not all equal.\n- Reject null, but not very useful finding.\n\n## Which groups are different from which?\n\n- ANOVA really only answers half our questions: it says \"there are\n differences\", but doesn't tell us which groups different.\n- One possibility (not the best): compare all possible pairs of\n groups, via two-sample t.\n- First pick out each group:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% filter(group==\"Control\") -> controls\nrats %>% filter(group==\"Lowjump\") -> lows\nrats %>% filter(group==\"Highjump\") -> highs\n```\n:::\n\n\n## Control vs. low\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, lows$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and lows$density\nt = -1.0761, df = 16.191, p-value = 0.2977\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -33.83725 11.03725\nsample estimates:\nmean of x mean of y \n 601.1 612.5 \n```\n:::\n:::\n\n\nNo sig. difference here.\n\n## Control vs. high\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and highs$density\nt = -3.7155, df = 14.831, p-value = 0.002109\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -59.19139 -16.00861\nsample estimates:\nmean of x mean of y \n 601.1 638.7 \n```\n:::\n:::\n\n\nThese are different.\n\n## Low vs. high\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(lows$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: lows$density and highs$density\nt = -3.2523, df = 17.597, p-value = 0.004525\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -43.15242 -9.24758\nsample estimates:\nmean of x mean of y \n 612.5 638.7 \n```\n:::\n:::\n\n\nThese are different too.\n\n## But...\n\n- We just did 3 tests instead of 1.\n- So we have given ourselves 3 chances to reject $H_0:$ all means\n equal, instead of 1.\n- Thus $\\alpha$ for this combined test is not 0.05.\n\n## John W. Tukey\n\n::: columns\n::: {.column width=\"40%\"}\n![](John_Tukey.jpg){width=\"400\"}\n:::\n\n::: {.column width=\"60%\"}\n- American statistician, 1915--2000\n- Big fan of exploratory data analysis\n- Popularized boxplot\n- Invented \"honestly significant differences\"\n- Invented jackknife estimation\n- Coined computing term \"bit\"\n- Co-inventor of Fast Fourier Transform\n:::\n:::\n\n## Honestly Significant Differences\n\n- Compare several groups with one test, telling you which groups\n differ from which.\n- Idea: if all population means equal, find distribution of highest\n sample mean minus lowest sample mean.\n- Any means unusually different compared to that declared\n significantly different.\n\n## Tukey on rat data\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group, data = rats)\nTukeyHSD(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = density ~ group, data = rats)\n\n$group\n diff lwr upr p adj\nHighjump-Control 37.6 13.66604 61.533957 0.0016388\nLowjump-Control 11.4 -12.53396 35.333957 0.4744032\nLowjump-Highjump -26.2 -50.13396 -2.266043 0.0297843\n```\n:::\n:::\n\n::: {.cell}\n\n:::\n\n\n- Again conclude that bone density for highjump group significantly\n higher than for other two groups.\n\n## Why Tukey's procedure better than all t-tests\n\nLook at P-values for the two tests:\n\n``` \nComparison Tukey t-tests\n----------------------------------\nHighjump-Control 0.0016 0.0021\nLowjump-Control 0.4744 0.2977\nLowjump-Highjump 0.0298 0.0045\n```\n\n- Tukey P-values (mostly) higher.\n- Proper adjustment for doing three t-tests at once, not just one in\n isolation.\n\n## Checking assumptions\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats,aes(y = density, x = fct_inorder(group)))+\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-21-1.png){width=960}\n:::\n:::\n\n\nAssumptions:\n\n- Normally distributed data within each group\n- with equal group SDs.\n\n## Normal quantile plots by group\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(sample = density)) + stat_qq() + \n stat_qq_line() + facet_wrap( ~ group)\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-revealjs/inference-5-R-22-1.png){width=960}\n:::\n:::\n\n\n## The assumptions\n\n- Normally-distributed data within each group\n- Equal group SDs.\n- These are shaky here because:\n - control group has outliers\n - highjump group appears to have less spread than others.\n- Possible remedies (in general):\n - Transformation of response (usually works best when SD increases\n with mean)\n - If normality OK but equal spreads not, can use Welch ANOVA.\n (Regular ANOVA like pooled t-test; Welch ANOVA like\n Welch-Satterthwaite t-test.)\n - Can also use Mood's Median Test (see over). This works for any\n number of groups.\n\n## Mood's median test here\n\n- Find median of all bone densities, regardless of group\n\n- Count up how many observations in each group above or below overall\n median\n\n- Test association between group and being above/below overall median,\n using chi-squared test.\n\n- Actually do this using `median_test`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(rats, density, group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 621.5\n\n$table\n above\ngroup above below\n Control 1 9\n Highjump 10 0\n Lowjump 4 6\n\n$test\n what value\n1 statistic 1.680000e+01\n2 df 2.000000e+00\n3 P-value 2.248673e-04\n```\n:::\n:::\n\n\n## Comments\n\n- No doubt that medians differ between groups (not all same).\n- This test is equivalent of $F$-test, not of Tukey.\n- To determine which groups differ from which, can compare all\n possible pairs of groups via (2-sample) Mood's median tests, then\n adjust P-values by multiplying by number of 2-sample Mood tests done\n (Bonferroni):\n\n\n::: {.cell}\n\n```{.r .cell-code}\npairwise_median_test(rats, density, group)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Now, lowjump-highjump difference no longer significant.\n\n## Welch ANOVA\n\n- For these data, Mood's median test probably best because we doubt\n both normality and equal spreads.\n- When normality OK but spreads differ, Welch ANOVA way to go.\n- Welch ANOVA done by `oneway.test` as shown (for illustration):\n\n\n::: {.cell}\n\n```{.r .cell-code}\noneway.test(density~group, data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne-way analysis of means (not assuming equal variances)\n\ndata: density and group\nF = 8.8164, num df = 2.000, denom df = 17.405, p-value = 0.002268\n```\n:::\n:::\n\n\n- P-value very similar, as expected.\n- Appropriate Tukey-equivalent here called Games-Howell.\n\n## Games-Howell\n\n- Lives in package `PMCMRplus`. Install first.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ngamesHowellTest(density~factor(group),data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Control Highjump\nHighjump 0.0056 - \nLowjump 0.5417 0.0120 \n```\n:::\n:::\n\n\n## Deciding which test to do\n\nFor two or more samples:\n\n![](testflow.png)\n", "supporting": [ "inference_5b_files/figure-revealjs" ], diff --git a/_freeze/inference_5b/execute-results/tex.json b/_freeze/inference_5b/execute-results/tex.json index 2680523..a286e1f 100644 --- a/_freeze/inference_5b/execute-results/tex.json +++ b/_freeze/inference_5b/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "3775a6bbfd15f9bddda4badaa71252aa", + "hash": "dcd78877625f55e2caecf5834490b26e", "result": { - "markdown": "---\ntitle: \"Analysis of variance\"\n---\n\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\nlibrary(PMCMRplus)\n```\n:::\n\n\n\n\n\n\n## Jumping rats\n- Link between exercise and healthy bones (many studies).\n- Exercise stresses bones and causes them to get stronger.\n- Study (Purdue): effect of jumping on bone density of growing rats.\n- 30 rats, randomly assigned to 1 of 3 treatments:\n - No jumping (control)\n - Low-jump treatment (30 cm)\n - High-jump treatment (60 cm)\n- 8 weeks, 10 jumps/day, 5 days/week.\n- Bone density of rats (mg/cm$^3$) measured at end.\n\n## Jumping rats 2/2\n\n- See whether larger amount of exercise (jumping) went with higher\nbone density.\n- Random assignment: rats in each group similar in all important ways.\n- So entitled to draw conclusions about cause and effect.\n\n## Reading the data\nValues separated by spaces:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/jumping.txt\"\nrats <- read_delim(my_url,\" \")\n```\n:::\n\n\n\n\\normalsize\n\n## The data (some random rows)\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% slice_sample(n=12)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 12 x 2\n group density\n \n 1 Highjump 643\n 2 Lowjump 599\n 3 Control 611\n 4 Lowjump 605\n 5 Lowjump 635\n 6 Lowjump 588\n 7 Control 621\n 8 Lowjump 632\n 9 Lowjump 596\n10 Highjump 650\n11 Control 554\n12 Lowjump 631\n```\n:::\n:::\n\n\n\\normalsize\n\n## Boxplots\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=group)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-11-1.pdf)\n:::\n:::\n\n\n\n## Or, arranging groups in data (logical) order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=fct_inorder(group))) +\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-12-1.pdf)\n:::\n:::\n\n\n\n## Analysis of Variance\n- Comparing > 2 groups of independent observations (each rat only\ndoes one amount of jumping).\n- Standard procedure: analysis of variance (ANOVA).\n- Null hypothesis: all groups have same mean.\n- Alternative: “not all means the same”, at least one is different from\nothers.\n\n## Testing: ANOVA in R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group,data=rats)\nsummary(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \ngroup 2 7434 3717 7.978 0.0019 **\nResiduals 27 12579 466 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n- Usual ANOVA table, small P-value: significant result.\n- Conclude that the mean bone densities are not all equal.\n- Reject null, but not very useful finding.\n\n## Which groups are different from which?\n- ANOVA really only answers half our questions: it says “there are\ndifferences”, but doesn’t tell us which groups different.\n- One possibility (not the best): compare all possible pairs of groups,\nvia two-sample t.\n- First pick out each group:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% filter(group==\"Control\") -> controls\nrats %>% filter(group==\"Lowjump\") -> lows\nrats %>% filter(group==\"Highjump\") -> highs\n```\n:::\n\n\n\n## Control vs. low\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, lows$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and lows$density\nt = -1.0761, df = 16.191, p-value = 0.2977\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -33.83725 11.03725\nsample estimates:\nmean of x mean of y \n 601.1 612.5 \n```\n:::\n:::\n\n\n\nNo sig. difference here.\n\n## Control vs. high\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and highs$density\nt = -3.7155, df = 14.831, p-value = 0.002109\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -59.19139 -16.00861\nsample estimates:\nmean of x mean of y \n 601.1 638.7 \n```\n:::\n:::\n\n\n\nThese are different.\n\n## Low vs. high\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(lows$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: lows$density and highs$density\nt = -3.2523, df = 17.597, p-value = 0.004525\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -43.15242 -9.24758\nsample estimates:\nmean of x mean of y \n 612.5 638.7 \n```\n:::\n:::\n\n\n\nThese are different too.\n\n## But...\n- We just did 3 tests instead of 1.\n- So we have given ourselves 3 chances to reject $H_0:$ all means equal,\ninstead of 1.\n- Thus $\\alpha$ for this combined test is not 0.05.\n\n## John W. Tukey \n\n:::: {.columns}\n\n::: {.column width=\"40%\"}\n![](John_Tukey.jpg){width=400}\n:::\n\n::: {.column width=\"60%\"}\n\n- American statistician, 1915--2000\n- Big fan of exploratory data analysis\n- Popularized boxplot\n- Invented \"honestly significant differences\"\n- Invented jackknife estimation\n- Coined computing term \"bit\"\n- Co-inventor of Fast Fourier Transform\n \n:::\n\n::::\n\n## Honestly Significant Differences\n- Compare several groups with one test, telling you which groups differ\nfrom which.\n- Idea: if all population means equal, find distribution of highest sample\nmean minus lowest sample mean.\n- Any means unusually different compared to that declared significantly\ndifferent.\n\n## Tukey on rat data\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group, data = rats)\nTukeyHSD(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = density ~ group, data = rats)\n\n$group\n diff lwr upr p adj\nHighjump-Control 37.6 13.66604 61.533957 0.0016388\nLowjump-Control 11.4 -12.53396 35.333957 0.4744032\nLowjump-Highjump -26.2 -50.13396 -2.266043 0.0297843\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n:::\n\n\n\n- Again conclude that bone density for highjump group significantly higher\nthan for other two groups.\n\n## Why Tukey’s procedure better than all t-tests \nLook at P-values for the two tests:\n\n```\nComparison Tukey t-tests\n----------------------------------\nHighjump-Control 0.0016 0.0021\nLowjump-Control 0.4744 0.2977\nLowjump-Highjump 0.0298 0.0045\n```\n\n \n- Tukey P-values (mostly) higher.\n- Proper adjustment for doing three t-tests at once, not just one in\nisolation.\n\n## Checking assumptions\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats,aes(y = density, x = fct_inorder(group)))+\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-21-1.pdf)\n:::\n:::\n\n\n\nAssumptions:\n\n- Normally distributed data within each group\n- with equal group SDs.\n\n## Normal quantile plots by group\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(sample = density)) + stat_qq() + \n stat_qq_line() + facet_wrap( ~ group)\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-22-1.pdf)\n:::\n:::\n\n\n\n## The assumptions\n- Normally-distributed data within each group\n- Equal group SDs.\n- These are shaky here because:\n - control group has outliers\n - highjump group appears to have less spread than others.\n- Possible remedies (in general):\n - Transformation of response (usually works best when SD increases\nwith mean)\n - If normality OK but equal spreads not, can use Welch ANOVA.\n(Regular ANOVA like pooled t-test; Welch ANOVA like\nWelch-Satterthwaite t-test.)\n - Can also use Mood’s Median Test (see over). This works for any\nnumber of groups.\n\n## Mood’s median test here\n\n- Find median of all bone densities, regardless of group\n- Count up how many observations in each group above or below\noverall median\n- Test association between group and being above/below\noverall median, using chi-squared test.\n\n- Actually do this using `median_test`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(rats, density, group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 621.5\n\n$table\n above\ngroup above below\n Control 1 9\n Highjump 10 0\n Lowjump 4 6\n\n$test\n what value\n1 statistic 1.680000e+01\n2 df 2.000000e+00\n3 P-value 2.248673e-04\n```\n:::\n:::\n\n\n\n\n\n## Comments\n- No doubt that medians differ between groups (not all same). \n- This test is equivalent of $F$-test, not of Tukey. \n- To determine which groups differ from which, can compare all possible\npairs of groups via (2-sample) Mood’s median tests, then adjust\nP-values by multiplying by number of 2-sample Mood tests done (Bonferroni):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npairwise_median_test(rats, density, group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 4\n g1 g2 p_value adj_p_value\n \n1 Control Highjump 0.000148 0.000443\n2 Control Lowjump 0.371 1 \n3 Highjump Lowjump 0.371 1 \n```\n:::\n:::\n\n\n\n- Now, lowjump-highjump difference no longer significant. \n\n## Welch ANOVA\n- For these data, Mood’s median test probably best because we doubt\nboth normality and equal spreads.\n- When normality OK but spreads differ, Welch ANOVA way to go.\n- Welch ANOVA done by `oneway.test` as shown (for illustration):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\noneway.test(density~group, data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne-way analysis of means (not assuming equal variances)\n\ndata: density and group\nF = 8.8164, num df = 2.000, denom df = 17.405, p-value = 0.002268\n```\n:::\n:::\n\n\n\n- P-value very similar, as expected.\n- Appropriate Tukey-equivalent here called Games-Howell.\n\n## Games-Howell\n\n- Lives in package `PMCMRplus`. Install\nfirst.\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ngamesHowellTest(density~factor(group),data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Control Highjump\nHighjump 0.0056 - \nLowjump 0.5417 0.0120 \n```\n:::\n:::\n\n\n\n## Deciding which test to do\n\nFor two or more samples:\n\n![](testflow.png)\n\n", + "markdown": "---\ntitle: \"Analysis of variance\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(smmr)\nlibrary(PMCMRplus)\n```\n:::\n\n\n\n## Jumping rats\n\n- Link between exercise and healthy bones (many studies).\n- Exercise stresses bones and causes them to get stronger.\n- Study (Purdue): effect of jumping on bone density of growing rats.\n- 30 rats, randomly assigned to 1 of 3 treatments:\n - No jumping (control)\n - Low-jump treatment (30 cm)\n - High-jump treatment (60 cm)\n- 8 weeks, 10 jumps/day, 5 days/week.\n- Bone density of rats (mg/cm$^3$) measured at end.\n\n## Jumping rats 2/2\n\n- See whether larger amount of exercise (jumping) went with higher\n bone density.\n- Random assignment: rats in each group similar in all important ways.\n- So entitled to draw conclusions about cause and effect.\n\n## Reading the data\n\nValues separated by spaces:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/jumping.txt\"\nrats <- read_delim(my_url,\" \")\n```\n:::\n\n\n\n## The data (some random rows)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% slice_sample(n=10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 2\n group density\n \n 1 Control 554\n 2 Highjump 626\n 3 Highjump 650\n 4 Highjump 631\n 5 Lowjump 588\n 6 Highjump 622\n 7 Control 600\n 8 Lowjump 596\n 9 Control 653\n10 Highjump 622\n```\n:::\n\n```{.r .cell-code}\nrats\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 30 x 2\n group density\n \n 1 Control 611\n 2 Control 621\n 3 Control 614\n 4 Control 593\n 5 Control 593\n 6 Control 653\n 7 Control 600\n 8 Control 554\n 9 Control 603\n10 Control 569\n# i 20 more rows\n```\n:::\n:::\n\n\n\n## Boxplots\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=group)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-11-1.pdf)\n:::\n:::\n\n\n\n## Or, arranging groups in data (logical) order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(y=density, x=fct_inorder(group))) +\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-12-1.pdf)\n:::\n:::\n\n\n\n## Analysis of Variance\n\n- Comparing \\> 2 groups of independent observations (each rat only\n does one amount of jumping).\n- Standard procedure: analysis of variance (ANOVA).\n- Null hypothesis: all groups have same mean.\n- Alternative: \"not all means the same\", at least one is different\n from others.\n\n## Testing: ANOVA in R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group,data=rats)\nsummary(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \ngroup 2 7434 3717 7.978 0.0019 **\nResiduals 27 12579 466 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n- Usual ANOVA table, small P-value: significant result.\n- Conclude that the mean bone densities are not all equal.\n- Reject null, but not very useful finding.\n\n## Which groups are different from which?\n\n- ANOVA really only answers half our questions: it says \"there are\n differences\", but doesn't tell us which groups different.\n- One possibility (not the best): compare all possible pairs of\n groups, via two-sample t.\n- First pick out each group:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats %>% filter(group==\"Control\") -> controls\nrats %>% filter(group==\"Lowjump\") -> lows\nrats %>% filter(group==\"Highjump\") -> highs\n```\n:::\n\n\n\n## Control vs. low\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, lows$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and lows$density\nt = -1.0761, df = 16.191, p-value = 0.2977\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -33.83725 11.03725\nsample estimates:\nmean of x mean of y \n 601.1 612.5 \n```\n:::\n:::\n\n\n\nNo sig. difference here.\n\n## Control vs. high\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(controls$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: controls$density and highs$density\nt = -3.7155, df = 14.831, p-value = 0.002109\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -59.19139 -16.00861\nsample estimates:\nmean of x mean of y \n 601.1 638.7 \n```\n:::\n:::\n\n\n\nThese are different.\n\n## Low vs. high\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt.test(lows$density, highs$density)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tWelch Two Sample t-test\n\ndata: lows$density and highs$density\nt = -3.2523, df = 17.597, p-value = 0.004525\nalternative hypothesis: true difference in means is not equal to 0\n95 percent confidence interval:\n -43.15242 -9.24758\nsample estimates:\nmean of x mean of y \n 612.5 638.7 \n```\n:::\n:::\n\n\n\nThese are different too.\n\n## But...\n\n- We just did 3 tests instead of 1.\n- So we have given ourselves 3 chances to reject $H_0:$ all means\n equal, instead of 1.\n- Thus $\\alpha$ for this combined test is not 0.05.\n\n## John W. Tukey\n\n::: columns\n::: {.column width=\"40%\"}\n![](John_Tukey.jpg){width=\"400\"}\n:::\n\n::: {.column width=\"60%\"}\n- American statistician, 1915--2000\n- Big fan of exploratory data analysis\n- Popularized boxplot\n- Invented \"honestly significant differences\"\n- Invented jackknife estimation\n- Coined computing term \"bit\"\n- Co-inventor of Fast Fourier Transform\n:::\n:::\n\n## Honestly Significant Differences\n\n- Compare several groups with one test, telling you which groups\n differ from which.\n- Idea: if all population means equal, find distribution of highest\n sample mean minus lowest sample mean.\n- Any means unusually different compared to that declared\n significantly different.\n\n## Tukey on rat data\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nrats.aov <- aov(density~group, data = rats)\nTukeyHSD(rats.aov)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = density ~ group, data = rats)\n\n$group\n diff lwr upr p adj\nHighjump-Control 37.6 13.66604 61.533957 0.0016388\nLowjump-Control 11.4 -12.53396 35.333957 0.4744032\nLowjump-Highjump -26.2 -50.13396 -2.266043 0.0297843\n```\n:::\n:::\n\n::: {.cell}\n\n:::\n\n\n\n- Again conclude that bone density for highjump group significantly\n higher than for other two groups.\n\n## Why Tukey's procedure better than all t-tests\n\nLook at P-values for the two tests:\n\n``` \nComparison Tukey t-tests\n----------------------------------\nHighjump-Control 0.0016 0.0021\nLowjump-Control 0.4744 0.2977\nLowjump-Highjump 0.0298 0.0045\n```\n\n- Tukey P-values (mostly) higher.\n- Proper adjustment for doing three t-tests at once, not just one in\n isolation.\n\n## Checking assumptions\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats,aes(y = density, x = fct_inorder(group)))+\n geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-21-1.pdf)\n:::\n:::\n\n\n\nAssumptions:\n\n- Normally distributed data within each group\n- with equal group SDs.\n\n## Normal quantile plots by group\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(rats, aes(sample = density)) + stat_qq() + \n stat_qq_line() + facet_wrap( ~ group)\n```\n\n::: {.cell-output-display}\n![](inference_5b_files/figure-beamer/inference-5-R-22-1.pdf)\n:::\n:::\n\n\n\n## The assumptions\n\n- Normally-distributed data within each group\n- Equal group SDs.\n- These are shaky here because:\n - control group has outliers\n - highjump group appears to have less spread than others.\n- Possible remedies (in general):\n - Transformation of response (usually works best when SD increases\n with mean)\n - If normality OK but equal spreads not, can use Welch ANOVA.\n (Regular ANOVA like pooled t-test; Welch ANOVA like\n Welch-Satterthwaite t-test.)\n - Can also use Mood's Median Test (see over). This works for any\n number of groups.\n\n## Mood's median test here\n\n- Find median of all bone densities, regardless of group\n\n- Count up how many observations in each group above or below overall\n median\n\n- Test association between group and being above/below overall median,\n using chi-squared test.\n\n- Actually do this using `median_test`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmedian_test(rats, density, group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$grand_median\n[1] 621.5\n\n$table\n above\ngroup above below\n Control 1 9\n Highjump 10 0\n Lowjump 4 6\n\n$test\n what value\n1 statistic 1.680000e+01\n2 df 2.000000e+00\n3 P-value 2.248673e-04\n```\n:::\n:::\n\n\n\n## Comments\n\n- No doubt that medians differ between groups (not all same).\n- This test is equivalent of $F$-test, not of Tukey.\n- To determine which groups differ from which, can compare all\n possible pairs of groups via (2-sample) Mood's median tests, then\n adjust P-values by multiplying by number of 2-sample Mood tests done\n (Bonferroni):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npairwise_median_test(rats, density, group)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 4\n g1 g2 p_value adj_p_value\n \n1 Control Highjump 0.000148 0.000443\n2 Control Lowjump 0.371 1 \n3 Highjump Lowjump 0.371 1 \n```\n:::\n:::\n\n\n\n- Now, lowjump-highjump difference no longer significant.\n\n## Welch ANOVA\n\n- For these data, Mood's median test probably best because we doubt\n both normality and equal spreads.\n- When normality OK but spreads differ, Welch ANOVA way to go.\n- Welch ANOVA done by `oneway.test` as shown (for illustration):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\noneway.test(density~group, data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\n\tOne-way analysis of means (not assuming equal variances)\n\ndata: density and group\nF = 8.8164, num df = 2.000, denom df = 17.405, p-value = 0.002268\n```\n:::\n:::\n\n\n\n- P-value very similar, as expected.\n- Appropriate Tukey-equivalent here called Games-Howell.\n\n## Games-Howell\n\n- Lives in package `PMCMRplus`. Install first.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ngamesHowellTest(density~factor(group),data=rats)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Control Highjump\nHighjump 0.0056 - \nLowjump 0.5417 0.0120 \n```\n:::\n:::\n\n\n\n## Deciding which test to do\n\nFor two or more samples:\n\n![](testflow.png)\n", "supporting": [ "inference_5b_files/figure-beamer" ], diff --git a/_freeze/inference_5b/figure-beamer/inference-5-R-11-1.pdf b/_freeze/inference_5b/figure-beamer/inference-5-R-11-1.pdf index a6faa32..0bca758 100644 Binary files a/_freeze/inference_5b/figure-beamer/inference-5-R-11-1.pdf and b/_freeze/inference_5b/figure-beamer/inference-5-R-11-1.pdf differ diff --git a/_freeze/inference_5b/figure-beamer/inference-5-R-12-1.pdf b/_freeze/inference_5b/figure-beamer/inference-5-R-12-1.pdf index e95518d..f61bdcb 100644 Binary files a/_freeze/inference_5b/figure-beamer/inference-5-R-12-1.pdf and b/_freeze/inference_5b/figure-beamer/inference-5-R-12-1.pdf differ diff --git a/_freeze/inference_5b/figure-beamer/inference-5-R-21-1.pdf b/_freeze/inference_5b/figure-beamer/inference-5-R-21-1.pdf index 1d262af..25205bf 100644 Binary files a/_freeze/inference_5b/figure-beamer/inference-5-R-21-1.pdf and b/_freeze/inference_5b/figure-beamer/inference-5-R-21-1.pdf differ diff --git a/_freeze/inference_5b/figure-beamer/inference-5-R-22-1.pdf b/_freeze/inference_5b/figure-beamer/inference-5-R-22-1.pdf index e0290b2..593cec5 100644 Binary files a/_freeze/inference_5b/figure-beamer/inference-5-R-22-1.pdf and b/_freeze/inference_5b/figure-beamer/inference-5-R-22-1.pdf differ diff --git a/_freeze/logistic/execute-results/html.json b/_freeze/logistic/execute-results/html.json index 16a8921..c3c68ce 100644 --- a/_freeze/logistic/execute-results/html.json +++ b/_freeze/logistic/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "d00d4260f0342837f54c858773a0870d", + "hash": "e6b7540e15c08007b53f23b153419a27", "result": { - "markdown": "---\ntitle: \"Logistic Regression\"\n---\n\n\n\n## Logistic regression\n\n\n* When response variable is measured/counted, regression can work well.\n\n* But what if response is yes/no, lived/died, success/failure?\n\n* Model *probability* of success.\n\n* Probability must be between 0 and 1; need method that ensures this.\n\n* *Logistic regression* does this. In R, is a\n*generalized linear model* with binomial \"family\": \n\n::: {.cell}\n\n```{.r .cell-code}\nglm(y ~ x, family=\"binomial\")\n```\n:::\n\n\n\n* Begin with simplest case.\n\n\n\n## Packages\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(marginaleffects)\nlibrary(broom)\nlibrary(nnet)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\nconflict_prefer(\"filter\", \"dplyr\")\nconflict_prefer(\"rename\", \"dplyr\")\nconflict_prefer(\"summarize\", \"dplyr\")\n```\n:::\n\n\n \n\n\n## The rats, part 1\n\n\n* Rats given dose of some poison; either live or die:\n\n\\small\n```\ndose status\n0 lived\n1 died\n2 lived\n3 lived\n4 died\n5 died\n```\n\n\\normalsize\n\n## Read in: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat.txt\"\nrats <- read_delim(my_url, \" \")\nrats\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n## Basic logistic regression\n\n\n* Make response into a factor first:\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nrats2 <- rats %>% mutate(status = factor(status))\n```\n:::\n\n\\normalsize\n \n\n\n* then fit model:\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nstatus.1 <- glm(status ~ dose, family = \"binomial\", data = rats2)\n```\n:::\n\n\\normalsize\n \n\n \n\n\n## Output\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(status.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = status ~ dose, family = \"binomial\", data = rats2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 1.6841 1.7979 0.937 0.349\ndose -0.6736 0.6140 -1.097 0.273\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 8.3178 on 5 degrees of freedom\nResidual deviance: 6.7728 on 4 degrees of freedom\nAIC: 10.773\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\\normalsize\n\n\n## Interpreting the output\n\n\n* Like (multiple) regression, get\ntests of significance of individual $x$'s\n\n* Here not significant (only 6 observations).\n\n* \"Slope\" for dose is negative, meaning that as dose increases, probability of event modelled (survival) decreases.\n\n\n\n\n\n## Output part 2: predicted survival probs\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(status.1)) %>% \n select(dose, estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize \n\n## On a graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(status.1, condition = \"dose\")\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-1-1.png){width=960}\n:::\n:::\n\n\n\n## The rats, more\n\n\n* More realistic: more rats at each dose (say 10).\n\n* Listing each rat on one line makes a big data file.\n\n* Use format below: dose, number of survivals, number of deaths.\n\n```\n\ndose lived died\n0 10 0\n1 7 3 \n2 6 4 \n3 4 6 \n4 2 8 \n5 1 9 \n\n```\n\n\n* 6 lines of data correspond to 60 actual rats.\n\n* Saved in `rat2.txt`.\n\n\n\n## These data\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat2.txt\"\nrat2 <- read_delim(my_url, \" \")\nrat2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n## Create response matrix:\n\n- Each row contains *multiple* observations.\n- Create *two-column* response:\n - \\#survivals in first column, \n - \\#deaths in second.\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nresponse <- with(rat2, cbind(lived, died))\nresponse\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n lived died\n[1,] 10 0\n[2,] 7 3\n[3,] 6 4\n[4,] 4 6\n[5,] 2 8\n[6,] 1 9\n```\n:::\n:::\n\n\\normalsize\n\n- Response is R `matrix`:\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nclass(response)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"matrix\" \"array\" \n```\n:::\n:::\n\n\\normalsize\n\n \n## Fit logistic regression\n\n- using response you just made:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrat2.1 <- glm(response ~ dose,\n family = \"binomial\",\n data = rat2\n)\n```\n:::\n\n\n\n\n\n## Output\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(rat2.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = response ~ dose, family = \"binomial\", data = rat2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 2.3619 0.6719 3.515 0.000439 ***\ndose -0.9448 0.2351 -4.018 5.87e-05 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 27.530 on 5 degrees of freedom\nResidual deviance: 2.474 on 4 degrees of freedom\nAIC: 18.94\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\\normalsize\n\n\n## Predicted survival probs\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = rat2.1, dose = 0:5)\ncbind(predictions(rat2.1, newdata = new))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n## On a picture\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(rat2.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, dose) %>% \n ggplot(aes(x = dose, y = estimate, \n ymin = conf.low, ymax = conf.high)) + \n geom_line() + geom_ribbon(alpha = 0.3)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-2-1.png){width=960}\n:::\n:::\n\n\n\n## Comments\n\n\n\n* Significant effect of dose. \n\n* Effect of larger dose is to *decrease* survival probability\n(\"slope\" negative; also see in decreasing predictions.)\n\n* Confidence intervals around prediction narrower (more data).\n\n## Multiple logistic regression\n\n\n* With more than one $x$, works much like multiple regression.\n\n* Example: study of patients with blood poisoning severe enough to warrant surgery. Relate survival to other potential risk factors.\n\n* Variables, 1=present, 0=absent:\n\n\n * survival (death from sepsis=1), response\n * shock\n * malnutrition\n * alcoholism\n * age (as numerical variable)\n * bowel infarction\n\n\n* See what relates to death.\n\n\n\n## Read in data\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/sepsis.txt\"\nsepsis <- read_delim(my_url, \" \")\n```\n:::\n\n \n## Make sure categoricals really are\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% \n mutate(across(-age, \\(x) factor(x))) -> sepsis\n```\n:::\n\n\n\n## The data (some)\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n## Fit model\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.1 <- glm(death ~ shock + malnut + alcohol + age +\n bowelinf,\nfamily = \"binomial\",\ndata = sepsis\n)\n```\n:::\n\n\n \n\n\n## Output part 1\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sepsis.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n\n* All P-values fairly small\n\n* but `malnut` not significant: remove.\n\n\n\n## Removing `malnut`\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 <- update(sepsis.1, . ~ . - malnut)\ntidy(sepsis.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n\n* Everything significant now.\n\n\n\n## Comments\n\n\n* Most of the original $x$'s helped predict death. Only `malnut` seemed not to add anything.\n\n* Removed `malnut` and tried again.\n\n* Everything remaining is significant (though `bowelinf`\nactually became *less* significant).\n\n* All coefficients are *positive*, so having any of the risk\nfactors (or being older)\n*increases* risk of death. \n\n\n## Predictions from model without \"malnut\"\n\n\n* A few (rows of original dataframe) chosen \"at random\":\n\n\\tiny\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% slice(c(4, 1, 2, 11, 32)) -> new\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, conf.low, conf.high, shock:bowelinf)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n## Comments \n\n* Survival chances pretty good if no risk factors, though decreasing with age.\n\n* Having more than one risk factor reduces survival chances dramatically.\n\n* Usually good job of predicting survival; sometimes death predicted to survive.\n\n## Another way to assess effects\n\nof `age`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sepsis.2, age = seq(30, 70, 10))\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Assessing age effect \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, shock:age)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Assessing shock effect\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(shock = c(0, 1), model = sepsis.2)\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, death:shock)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Assessing proportionality of odds for age\n\n\n* An assumption we made is that log-odds of survival depends\nlinearly on age.\n\n* Hard to get your head around, but \nbasic idea is that survival chances go continuously up (or down)\nwith age, instead of (for example) going up and then down.\n\n* In this case, seems reasonable, but should check:\n\n\n## Residuals vs.\\ age\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 %>% augment(sepsis) %>% \n ggplot(aes(x = age, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/virtusentella-1.png){width=960}\n:::\n:::\n\n \n## Comments\n\n* No apparent problems overall.\n\n* Confusing \"line\" across: no risk factors, survived. \n\n\n\n## Probability and odds\n\n* For probability $p$, odds is $p/(1-p)$:\n\n\n \\begin{tabular}{rrrl}\n \\hline\n Prob.\\ & Odds & log-odds & in words\\\\\n \\hline\n 0.5 & $0.5/0.5=1/1=1.00$ & $0.00$ & ``even money''\\\\\n 0.1 & $0.1/0.9=1/9=0.11$ & $-2.20$ & ``9 to 1''\\\\\n 0.4 & $0.4/0.6=1/1.5=0.67$ & $-0.41$ & ``1.5 to 1''\\\\\n 0.8 & $0.8/0.2=4/1=4.00$ & $1.39$ & ``4 to 1 on''\\\\\n \\hline\n \\end{tabular}\n\n\n* Gamblers use odds: if you win at 9 to 1 odds, get original\nstake back plus 9 times the stake.\n\n* Probability has to be between 0 and 1\n\n* Odds between 0 and infinity\n\n* *Log*-odds can be anything: any log-odds corresponds to\nvalid probability.\n\n\n\n## Odds ratio\n\n\n* Suppose 90 of 100 men drank wine last week, but only 20 of 100 women.\n\n* Prob of man drinking wine $90/100=0.9$, woman $20/100=0.2$.\n\n* Odds of man drinking wine $0.9/0.1=9$, woman $0.2/0.8=0.25$.\n\n* Ratio of odds is $9/0.25=36$.\n\n* Way of quantifying difference between men and women: ``odds of\ndrinking wine 36 times larger for males than females''. \n\n\n\n## Sepsis data again\n\n\n* Recall prediction of probability of death from risk factors:\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy <- tidy(sepsis.2)\nsepsis.2.tidy\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n* Slopes in column `estimate`.\n\n\n\n## Multiplying the odds\n\n\n* Can interpret slopes by taking \"exp\" of them. We ignore intercept.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy %>% \n mutate(exp_coeff=exp(estimate)) %>% \n select(term, exp_coeff)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Interpretation\n\n\\small\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n* These say \"how much do you *multiply* odds of death by\nfor increase of 1 in corresponding risk factor?\" Or, what is odds\nratio for that factor being 1 (present) vs.\\ 0 (absent)?\n\n* Eg.\\ being alcoholic vs.\\ not increases odds of death by 24 times\n\n* One year older multiplies odds by about 1.1 times. Over 40 years,\nabout $1.09^{40}=31$ times. \n\n\n\n## Odds ratio and relative risk\n\n\n* **Relative risk** is ratio of probabilities.\n\n* Above: 90 of 100 men (0.9) drank wine, 20 of 100 women (0.2).\n\n* Relative risk 0.9/0.2=4.5. (odds ratio was 36).\n\n* When probabilities small, relative risk and odds ratio similar.\n\n* Eg.\\ prob of man having disease 0.02, woman 0.01.\n\n* Relative risk $0.02/0.01=2$.\n\n## Odds ratio vs.\\ relative risk\n\n- Odds for men and for women:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(od1 <- 0.02 / 0.98) # men\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.02040816\n```\n:::\n\n```{.r .cell-code}\n(od2 <- 0.01 / 0.99) # women\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.01010101\n```\n:::\n:::\n\n\n- Odds ratio \n\n\n::: {.cell}\n\n```{.r .cell-code}\nod1 / od2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.020408\n```\n:::\n:::\n\n\n\n- Very close to relative risk of 2.\n\n\n## More than 2 response categories\n\n\n* With 2 response categories, model the probability of one, and prob of other is one minus that. So doesn't matter which category you model.\n\n* With more than 2 categories, have to think more carefully about the categories: are they\n\n\n* *ordered*: you can put them in a natural order (like low, medium, high)\n\n* *nominal*: ordering the categories doesn't make sense (like red, green, blue).\n\n\n* R handles both kinds of response; learn how.\n\n\n\n## Ordinal response: the miners\n\n\n* \nModel probability of being in given category *or lower*.\n\n* Example: coal-miners often suffer disease pneumoconiosis. Likelihood of disease believed to be greater \namong miners who have worked longer. \n\n* Severity of disease measured on categorical scale: none,\nmoderate, severe.\n\n## Miners data\n\n* Data are frequencies:\n\n```\nExposure None Moderate Severe\n5.8 98 0 0\n15.0 51 2 1\n21.5 34 6 3\n27.5 35 5 8\n33.5 32 10 9\n39.5 23 7 8\n46.0 12 6 10\n51.5 4 2 5\n```\n\n\n\n\n## Reading the data\n\nData in aligned columns with more than one space between, so: \n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/miners-tab.txt\"\nfreqs <- read_table(my_url)\n```\n:::\n\n\\normalsize\n\n\n## The data\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n## Tidying \n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>%\n pivot_longer(-Exposure, names_to = \"Severity\", values_to = \"Freq\") %>%\n mutate(Severity = fct_inorder(Severity)) -> miners\n```\n:::\n\n\n \n\n\n## Result\n\n\\tiny\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n \n\n\n## Plot proportions against exposure\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nminers %>% \n group_by(Exposure) %>% \n mutate(proportion = Freq / sum(Freq)) -> prop\nggplot(prop, aes(x = Exposure, y = proportion,\n colour = Severity)) + \n geom_point() + geom_smooth(se = F)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/bLogistic-28-1.png){width=960}\n:::\n:::\n\n\\normalsize\n\n\n## Reminder of data setup\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\\normalsize\n\n\n\n## Fitting ordered logistic model\n\nUse function `polr` from package `MASS`. Like `glm`.\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners\n)\n```\n:::\n\n \n\n\n## Output: not very illuminating\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners,\n Hess = TRUE\n)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sev.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\npolr(formula = Severity ~ Exposure, data = miners, weights = Freq, \n Hess = TRUE)\n\nCoefficients:\n Value Std. Error t value\nExposure 0.0959 0.01194 8.034\n\nIntercepts:\n Value Std. Error t value\nNone|Moderate 3.9558 0.4097 9.6558\nModerate|Severe 4.8690 0.4411 11.0383\n\nResidual Deviance: 416.9188 \nAIC: 422.9188 \n```\n:::\n:::\n\n\\normalsize\n \n\n## Does exposure have an effect?\nFit model without `Exposure`, and compare\nusing `anova`. Note `1` for model with just intercept:\n\n\n::: {.cell}\n\n:::\n\n\n \n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.0 <- polr(Severity ~ 1, weights = Freq, data = miners)\nanova(sev.0, sev.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nExposure definitely has effect on severity of disease. \n\n\n## Another way\n\n\n* What (if anything) can we drop from model with `exposure`?\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(sev.1, test = \"Chisq\")\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n* Nothing. Exposure definitely has effect.\n\n\n\n## Predicted probabilities 1/2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>% select(Exposure) -> new\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Predicted probabilities 2/2\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sev.1, newdata = new)) %>%\n select(group, estimate, Exposure) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Plot of predicted probabilities\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(model = sev.1, condition = c(\"Exposure\", \"group\"),\n type = \"probs\") +\n geom_point(data = prop, aes(x = Exposure, y = proportion, \n colour = Severity)) -> ggg\n```\n:::\n\n\n## The graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggg\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-11-1.png){width=960}\n:::\n:::\n\n\n\n## Comments\n\n\n* Model appears to match data well enough.\n\n- As exposure goes up, prob of None\ngoes down, Severe goes up (sharply for high exposure).\n\n- So more exposure means worse disease.\n\n\n## Unordered responses\n\n\n* With unordered (nominal) responses, can use *generalized logit*.\n\n* Example: 735 people, record age and sex (male 0, female 1), which of 3 brands of some product preferred.\n\n* Data in `mlogit.csv` separated by commas (so\n`read_csv` will work):\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/mlogit.csv\"\nbrandpref <- read_csv(my_url)\n```\n:::\n\n \n\n\n\n\n## The data (some)\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n## Bashing into shape\n\n\n* `sex` and `brand` not meaningful as numbers, so\nturn into factors:\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n mutate(sex = ifelse(sex == 1, \"female\", \"male\"), \n sex = factor(sex),\n brand = factor(brand)\n ) -> brandpref\n```\n:::\n\n \n \n## Fitting model \n\n* We use `multinom` from package `nnet`. Works\nlike `polr`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.1 <- multinom(brand ~ age + sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 12 (6 variable)\ninitial value 807.480032 \niter 10 value 702.990572\nfinal value 702.970704 \nconverged\n```\n:::\n:::\n\n \n\n\n## Can we drop anything?\n\n\n* Unfortunately `drop1` seems not to work:\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(brands.1, test = \"Chisq\", trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \n```\n:::\n\n::: {.cell-output .cell-output-error}\n```\nError in if (trace) {: argument is not interpretable as logical\n```\n:::\n:::\n\n\n* So, fall back on fitting model without what you want to test, and\ncomparing using `anova`. \n\n\n\n## Do age/sex help predict brand? 1/3\n\nFit models without each of age and sex:\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.2 <- multinom(brand ~ age, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \niter 10 value 706.796323\niter 10 value 706.796322\nfinal value 706.796322 \nconverged\n```\n:::\n\n```{.r .cell-code}\nbrands.3 <- multinom(brand ~ sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \nfinal value 791.861266 \nconverged\n```\n:::\n:::\n\n \n\n\n## Do age/sex help predict brand? 2/3\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(brands.2, brands.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nanova(brands.3, brands.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n## Do age/sex help predict brand? 3/3\n\n\n* `age` definitely significant (second `anova`)\n\n* `sex` significant also (first `anova`), though P-value less dramatic\n\n* Keep both.\n- Expect to see a large effect of `age`, and a smaller one of `sex`.\n\n\n\n\n## Another way to build model\n\n\n* Start from model with everything and run `step`:\n\n\n::: {.cell}\n\n:::\n\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(brands.1, trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \ntrying - sex \n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\nmultinom(formula = brand ~ age + sex)\n\nCoefficients:\n (Intercept) age sexmale\n2 -11.25127 0.3682202 -0.5237736\n3 -22.25571 0.6859149 -0.4658215\n\nResidual Deviance: 1405.941 \nAIC: 1417.941 \n```\n:::\n:::\n\n\\normalsize\n \n\n* Final model contains both `age` and `sex` so neither\ncould be removed.\n\n\n## Making predictions\n\nFind age 5-number summary, and the two sexes:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n brand sex age \n 1:207 female:466 Min. :24.0 \n 2:307 male :269 1st Qu.:32.0 \n 3:221 Median :32.0 \n Mean :32.9 \n 3rd Qu.:34.0 \n Max. :38.0 \n```\n:::\n:::\n\n\nSpace the ages out a bit for prediction (see over).\n\n\\normalsize\n\n## Combinations\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(age = c(24, 30, 33, 35, 38), \n sex = c(\"female\", \"male\"), model = brands.1)\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## The predictions\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(brands.1, newdata = new)) %>%\n select(group, estimate, age, sex) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n* Young males prefer brand 1, \nbut older males prefer brand 3.\n\n* Females similar, but like brand 1 less and\nbrand 2 more.\n\n- A clear `brand` effect, but the `sex` effect is less clear. \n\n## Making a plot\n\n- `plot_cap` doesn't quite work\n- so don't draw, edit, *then* make graph:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(brands.1, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g\n```\n:::\n\n\n## The graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-16-1.png){width=960}\n:::\n:::\n\n \n\n## Digesting the plot\n\n\n* Brand vs.\\ age: younger people (of both genders) prefer brand\n1, but older people (of both genders) prefer brand 3. (Explains\nsignificant age effect.)\n\n* Brand vs.\\ sex: females (solid) like brand 1 less than males\n(dashed), like brand 2 more (for all ages). \n\n* Not much brand difference between genders (solid and dashed\nlines of same colours close), but enough to be significant.\n\n* Model didn't include interaction, so modelled effect of gender\non brand same for each age, modelled effect of age same for each\ngender. (See also later.) \n\n\n## Alternative data format\n\nSummarize all people of same brand preference, same sex, same age on one line of data file with frequency on end:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n```\n1 0 24 1\n1 0 26 2\n1 0 27 4\n1 0 28 4\n1 0 29 7\n1 0 30 3\n...\n```\n\nWhole data set in 65 lines not 735! But how?\n\n\n## Getting alternative data format\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n group_by(age, sex, brand) %>%\n summarize(Freq = n()) %>%\n ungroup() -> b\nb\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n## Fitting models, almost the same\n\n\n* Just have to remember `weights` to incorporate\nfrequencies.\n\n* Otherwise `multinom` assumes you have just 1 obs\non each line!\n\n* Again turn (numerical) `sex` and `brand` into factors:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nb %>%\n mutate(sex = factor(sex)) %>%\n mutate(brand = factor(brand)) -> bf\nb.1 <- multinom(brand ~ age + sex, data = bf, weights = Freq)\nb.2 <- multinom(brand ~ age, data = bf, weights = Freq)\n```\n:::\n\n\\normalsize\n\n\n\n## P-value for `sex` identical\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(b.2, b.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nSame P-value as before, so we haven't changed anything important.\n\n\n\n\n## Trying interaction between age and gender\n\n\n::: {.cell}\n\n:::\n\n\n \n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.4 <- update(brands.1, . ~ . + age:sex)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 15 (8 variable)\ninitial value 807.480032 \niter 10 value 703.191146\niter 20 value 702.572260\niter 30 value 702.570900\niter 30 value 702.570893\niter 30 value 702.570893\nfinal value 702.570893 \nconverged\n```\n:::\n\n```{.r .cell-code}\nanova(brands.1, brands.4)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\\normalsize\n\n* No evidence that effect of age on brand preference differs for\nthe two genders.\n\n## Make graph again\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(brands.4, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g4\n```\n:::\n\n\n\n## Not much difference in the graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng4\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-19-1.png){width=960}\n:::\n:::\n\n\n## Compare model without interaction\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-20-1.png){width=960}\n:::\n:::\n", + "markdown": "---\ntitle: \"Logistic Regression\"\n---\n\n\n\n## Logistic regression\n\n\n* When response variable is measured/counted, regression can work well.\n\n* But what if response is yes/no, lived/died, success/failure?\n\n* Model *probability* of success.\n\n* Probability must be between 0 and 1; need method that ensures this.\n\n* *Logistic regression* does this. In R, is a\n*generalized linear model* with binomial \"family\": \n\n::: {.cell}\n\n```{.r .cell-code}\nglm(y ~ x, family=\"binomial\")\n```\n:::\n\n\n\n* Begin with simplest case.\n\n\n\n## Packages\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(marginaleffects)\nlibrary(broom)\nlibrary(nnet)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\nconflict_prefer(\"filter\", \"dplyr\")\nconflict_prefer(\"rename\", \"dplyr\")\nconflict_prefer(\"summarize\", \"dplyr\")\n```\n:::\n\n\n \n\n\n## The rats, part 1\n\n\n* Rats given dose of some poison; either live or die:\n\n\\small\n```\ndose status\n0 lived\n1 died\n2 lived\n3 lived\n4 died\n5 died\n```\n\n\\normalsize\n\n## Read in: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat.txt\"\nrats <- read_delim(my_url, \" \")\nrats\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n## Basic logistic regression\n\n\n* Make response into a factor first:\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nrats2 <- rats %>% mutate(status = factor(status))\n```\n:::\n\n\\normalsize\n \n\n\n* then fit model:\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nstatus.1 <- glm(status ~ dose, family = \"binomial\", data = rats2)\n```\n:::\n\n\\normalsize\n \n\n \n\n\n## Output\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(status.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = status ~ dose, family = \"binomial\", data = rats2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 1.6841 1.7979 0.937 0.349\ndose -0.6736 0.6140 -1.097 0.273\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 8.3178 on 5 degrees of freedom\nResidual deviance: 6.7728 on 4 degrees of freedom\nAIC: 10.773\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\\normalsize\n\n\n## Interpreting the output\n\n\n* Like (multiple) regression, get\ntests of significance of individual $x$'s\n\n* Here not significant (only 6 observations).\n\n* \"Slope\" for dose is negative, meaning that as dose increases, probability of event modelled (survival) decreases.\n\n\n\n\n\n## Output part 2: predicted survival probs\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(status.1)) %>% \n select(dose, estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize \n\n## On a graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(status.1, condition = \"dose\")\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-1-1.png){width=960}\n:::\n:::\n\n\n\n## The rats, more\n\n\n* More realistic: more rats at each dose (say 10).\n\n* Listing each rat on one line makes a big data file.\n\n* Use format below: dose, number of survivals, number of deaths.\n\n```\n\ndose lived died\n0 10 0\n1 7 3 \n2 6 4 \n3 4 6 \n4 2 8 \n5 1 9 \n\n```\n\n\n* 6 lines of data correspond to 60 actual rats.\n\n* Saved in `rat2.txt`.\n\n\n\n## These data\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat2.txt\"\nrat2 <- read_delim(my_url, \" \")\nrat2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n## Create response matrix:\n\n- Each row contains *multiple* observations.\n- Create *two-column* response:\n - \\#survivals in first column, \n - \\#deaths in second.\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nresponse <- with(rat2, cbind(lived, died))\nresponse\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n lived died\n[1,] 10 0\n[2,] 7 3\n[3,] 6 4\n[4,] 4 6\n[5,] 2 8\n[6,] 1 9\n```\n:::\n:::\n\n\\normalsize\n\n- Response is R `matrix`:\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nclass(response)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"matrix\" \"array\" \n```\n:::\n:::\n\n\\normalsize\n\n \n## Fit logistic regression\n\n- using response you just made:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrat2.1 <- glm(response ~ dose,\n family = \"binomial\",\n data = rat2\n)\n```\n:::\n\n\n\n\n\n## Output\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(rat2.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = response ~ dose, family = \"binomial\", data = rat2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 2.3619 0.6719 3.515 0.000439 ***\ndose -0.9448 0.2351 -4.018 5.87e-05 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 27.530 on 5 degrees of freedom\nResidual deviance: 2.474 on 4 degrees of freedom\nAIC: 18.94\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\\normalsize\n\n\n## Predicted survival probs\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = rat2.1, dose = 0:5)\ncbind(predictions(rat2.1, newdata = new))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n## On a picture\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(rat2.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, dose) %>% \n ggplot(aes(x = dose, y = estimate, \n ymin = conf.low, ymax = conf.high)) + \n geom_line() + geom_ribbon(alpha = 0.3)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-2-1.png){width=960}\n:::\n:::\n\n\n\n## Comments\n\n\n\n* Significant effect of dose. \n\n* Effect of larger dose is to *decrease* survival probability\n(\"slope\" negative; also see in decreasing predictions.)\n\n* Confidence intervals around prediction narrower (more data).\n\n## Multiple logistic regression\n\n\n* With more than one $x$, works much like multiple regression.\n\n* Example: study of patients with blood poisoning severe enough to warrant surgery. Relate survival to other potential risk factors.\n\n* Variables, 1=present, 0=absent:\n\n\n * survival (death from sepsis=1), response\n * shock\n * malnutrition\n * alcoholism\n * age (as numerical variable)\n * bowel infarction\n\n\n* See what relates to death.\n\n\n\n## Read in data\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/sepsis.txt\"\nsepsis <- read_delim(my_url, \" \")\n```\n:::\n\n \n## Make sure categoricals really are\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% \n mutate(across(-age, \\(x) factor(x))) -> sepsis\n```\n:::\n\n\n\n## The data (some)\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n## Fit model\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.1 <- glm(death ~ shock + malnut + alcohol + age +\n bowelinf,\nfamily = \"binomial\",\ndata = sepsis\n)\n```\n:::\n\n\n \n\n\n## Output part 1\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sepsis.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n\n* All P-values fairly small\n\n* but `malnut` not significant: remove.\n\n\n\n## Removing `malnut`\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 <- update(sepsis.1, . ~ . - malnut)\ntidy(sepsis.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n\n\n* Everything significant now.\n\n\n\n## Comments\n\n\n* Most of the original $x$'s helped predict death. Only `malnut` seemed not to add anything.\n\n* Removed `malnut` and tried again.\n\n* Everything remaining is significant (though `bowelinf`\nactually became *less* significant).\n\n* All coefficients are *positive*, so having any of the risk\nfactors (or being older)\n*increases* risk of death. \n\n\n## Predictions from model without \"malnut\"\n\n\n* A few (rows of original dataframe) chosen \"at random\":\n\n\\tiny\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% slice(c(4, 1, 2, 11, 32)) -> new\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, conf.low, conf.high, shock:bowelinf)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n## Comments \n\n* Survival chances pretty good if no risk factors, though decreasing with age.\n\n* Having more than one risk factor reduces survival chances dramatically.\n\n* Usually good job of predicting survival; sometimes death predicted to survive.\n\n## Another way to assess effects\n\nof `age`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sepsis.2, age = seq(30, 70, 10))\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Assessing age effect \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, shock:age)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Assessing shock effect\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(shock = c(0, 1), model = sepsis.2)\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, death:shock)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Assessing proportionality of odds for age\n\n\n* An assumption we made is that log-odds of survival depends\nlinearly on age.\n\n* Hard to get your head around, but \nbasic idea is that survival chances go continuously up (or down)\nwith age, instead of (for example) going up and then down.\n\n* In this case, seems reasonable, but should check:\n\n\n## Residuals vs.\\ age\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 %>% augment(sepsis) %>% \n ggplot(aes(x = age, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/virtusentella-1.png){width=960}\n:::\n:::\n\n \n## Comments\n\n* No apparent problems overall.\n\n* Confusing \"line\" across: no risk factors, survived. \n\n\n\n## Probability and odds\n\n* For probability $p$, odds is $p/(1-p)$:\n\n\n \\begin{tabular}{rrrl}\n \\hline\n Prob.\\ & Odds & log-odds & in words\\\\\n \\hline\n 0.5 & $0.5/0.5=1/1=1.00$ & $0.00$ & ``even money''\\\\\n 0.1 & $0.1/0.9=1/9=0.11$ & $-2.20$ & ``9 to 1''\\\\\n 0.4 & $0.4/0.6=1/1.5=0.67$ & $-0.41$ & ``1.5 to 1''\\\\\n 0.8 & $0.8/0.2=4/1=4.00$ & $1.39$ & ``4 to 1 on''\\\\\n \\hline\n \\end{tabular}\n\n\n* Gamblers use odds: if you win at 9 to 1 odds, get original\nstake back plus 9 times the stake.\n\n* Probability has to be between 0 and 1\n\n* Odds between 0 and infinity\n\n* *Log*-odds can be anything: any log-odds corresponds to\nvalid probability.\n\n\n\n## Odds ratio\n\n\n* Suppose 90 of 100 men drank wine last week, but only 20 of 100 women.\n\n* Prob of man drinking wine $90/100=0.9$, woman $20/100=0.2$.\n\n* Odds of man drinking wine $0.9/0.1=9$, woman $0.2/0.8=0.25$.\n\n* Ratio of odds is $9/0.25=36$.\n\n* Way of quantifying difference between men and women: ``odds of\ndrinking wine 36 times larger for males than females''. \n\n\n\n## Sepsis data again\n\n\n* Recall prediction of probability of death from risk factors:\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy <- tidy(sepsis.2)\nsepsis.2.tidy\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n* Slopes in column `estimate`.\n\n\n\n## Multiplying the odds\n\n\n* Can interpret slopes by taking \"exp\" of them. We ignore intercept.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy %>% \n mutate(exp_coeff=exp(estimate)) %>% \n select(term, exp_coeff)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Interpretation\n\n\\small\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n* These say \"how much do you *multiply* odds of death by\nfor increase of 1 in corresponding risk factor?\" Or, what is odds\nratio for that factor being 1 (present) vs.\\ 0 (absent)?\n\n* Eg.\\ being alcoholic vs.\\ not increases odds of death by 24 times\n\n* One year older multiplies odds by about 1.1 times. Over 40 years,\nabout $1.09^{40}=31$ times. \n\n\n\n## Odds ratio and relative risk\n\n\n* **Relative risk** is ratio of probabilities.\n\n* Above: 90 of 100 men (0.9) drank wine, 20 of 100 women (0.2).\n\n* Relative risk 0.9/0.2=4.5. (odds ratio was 36).\n\n* When probabilities small, relative risk and odds ratio similar.\n\n* Eg.\\ prob of man having disease 0.02, woman 0.01.\n\n* Relative risk $0.02/0.01=2$.\n\n## Odds ratio vs.\\ relative risk\n\n- Odds for men and for women:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(od1 <- 0.02 / 0.98) # men\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.02040816\n```\n:::\n\n```{.r .cell-code}\n(od2 <- 0.01 / 0.99) # women\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.01010101\n```\n:::\n:::\n\n\n- Odds ratio \n\n\n::: {.cell}\n\n```{.r .cell-code}\nod1 / od2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.020408\n```\n:::\n:::\n\n\n\n- Very close to relative risk of 2.\n\n\n## More than 2 response categories\n\n\n* With 2 response categories, model the probability of one, and prob of other is one minus that. So doesn't matter which category you model.\n\n* With more than 2 categories, have to think more carefully about the categories: are they\n\n\n* *ordered*: you can put them in a natural order (like low, medium, high)\n\n* *nominal*: ordering the categories doesn't make sense (like red, green, blue).\n\n\n* R handles both kinds of response; learn how.\n\n\n\n## Ordinal response: the miners\n\n\n* \nModel probability of being in given category *or lower*.\n\n* Example: coal-miners often suffer disease pneumoconiosis. Likelihood of disease believed to be greater \namong miners who have worked longer. \n\n* Severity of disease measured on categorical scale: none,\nmoderate, severe.\n\n## Miners data\n\n* Data are frequencies:\n\n```\nExposure None Moderate Severe\n5.8 98 0 0\n15.0 51 2 1\n21.5 34 6 3\n27.5 35 5 8\n33.5 32 10 9\n39.5 23 7 8\n46.0 12 6 10\n51.5 4 2 5\n```\n\n\n\n\n## Reading the data\n\nData in aligned columns with more than one space between, so: \n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/miners-tab.txt\"\nfreqs <- read_table(my_url)\n```\n:::\n\n\\normalsize\n\n\n## The data\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n## Tidying \n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>%\n pivot_longer(-Exposure, names_to = \"Severity\", values_to = \"Freq\") %>%\n mutate(Severity = fct_inorder(Severity)) -> miners\n```\n:::\n\n\n \n\n\n## Result\n\n\\tiny\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n \n\n\n## Plot proportions against exposure\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nminers %>% \n group_by(Exposure) %>% \n mutate(proportion = Freq / sum(Freq)) -> prop\nggplot(prop, aes(x = Exposure, y = proportion,\n colour = Severity)) + \n geom_point() + geom_smooth(se = F)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/bLogistic-28-1.png){width=960}\n:::\n:::\n\n\\normalsize\n\n\n## Reminder of data setup\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\\normalsize\n\n\n\n## Fitting ordered logistic model\n\nUse function `polr` from package `MASS`. Like `glm`.\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners\n)\n```\n:::\n\n \n\n\n## Output: not very illuminating\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners,\n Hess = TRUE\n)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sev.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\npolr(formula = Severity ~ Exposure, data = miners, weights = Freq, \n Hess = TRUE)\n\nCoefficients:\n Value Std. Error t value\nExposure 0.0959 0.01194 8.034\n\nIntercepts:\n Value Std. Error t value\nNone|Moderate 3.9558 0.4097 9.6558\nModerate|Severe 4.8690 0.4411 11.0383\n\nResidual Deviance: 416.9188 \nAIC: 422.9188 \n```\n:::\n:::\n\n\\normalsize\n \n\n## Does exposure have an effect?\nFit model without `Exposure`, and compare\nusing `anova`. Note `1` for model with just intercept:\n\n\n::: {.cell}\n\n:::\n\n\n \n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.0 <- polr(Severity ~ 1, weights = Freq, data = miners)\nanova(sev.0, sev.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nExposure definitely has effect on severity of disease. \n\n\n## Another way\n\n\n* What (if anything) can we drop from model with `exposure`?\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(sev.1, test = \"Chisq\")\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n* Nothing. Exposure definitely has effect.\n\n\n\n## Predicted probabilities 1/2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>% select(Exposure) -> new\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Predicted probabilities 2/2\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sev.1, newdata = new)) %>%\n select(group, estimate, Exposure) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Plot of predicted probabilities\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(model = sev.1, condition = c(\"Exposure\", \"group\"),\n type = \"probs\") +\n geom_point(data = prop, aes(x = Exposure, y = proportion, \n colour = Severity)) -> ggg\n```\n:::\n\n\n## The graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggg\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-11-1.png){width=960}\n:::\n:::\n\n\n\n## Comments\n\n\n* Model appears to match data well enough.\n\n- As exposure goes up, prob of None\ngoes down, Severe goes up (sharply for high exposure).\n\n- So more exposure means worse disease.\n\n\n## Unordered responses\n\n\n* With unordered (nominal) responses, can use *generalized logit*.\n\n* Example: 735 people, record age and sex (male 0, female 1), which of 3 brands of some product preferred.\n\n* Data in `mlogit.csv` separated by commas (so\n`read_csv` will work):\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/mlogit.csv\"\nbrandpref <- read_csv(my_url)\n```\n:::\n\n \n\n\n\n\n## The data (some)\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n\n## Bashing into shape\n\n\n* `sex` and `brand` not meaningful as numbers, so\nturn into factors:\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n mutate(sex = ifelse(sex == 1, \"female\", \"male\"), \n sex = factor(sex),\n brand = factor(brand)\n ) -> brandpref\n```\n:::\n\n \n \n## Fitting model \n\n* We use `multinom` from package `nnet`. Works\nlike `polr`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.1 <- multinom(brand ~ age + sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 12 (6 variable)\ninitial value 807.480032 \niter 10 value 702.990572\nfinal value 702.970704 \nconverged\n```\n:::\n:::\n\n \n\n\n## Can we drop anything?\n\n\n* Unfortunately `drop1` seems not to work:\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(brands.1, test = \"Chisq\", trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \n```\n:::\n\n::: {.cell-output .cell-output-error}\n```\nError in if (trace) {: argument is not interpretable as logical\n```\n:::\n:::\n\n\n* So, fall back on fitting model without what you want to test, and\ncomparing using `anova`. \n\n\n\n## Do age/sex help predict brand? 1/3\n\nFit models without each of age and sex:\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.2 <- multinom(brand ~ age, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \niter 10 value 706.796323\niter 10 value 706.796322\nfinal value 706.796322 \nconverged\n```\n:::\n\n```{.r .cell-code}\nbrands.3 <- multinom(brand ~ sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \nfinal value 791.861266 \nconverged\n```\n:::\n:::\n\n \n\n\n## Do age/sex help predict brand? 2/3\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(brands.2, brands.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nanova(brands.3, brands.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n\n## Do age/sex help predict brand? 3/3\n\n\n* `age` definitely significant (second `anova`)\n\n* `sex` significant also (first `anova`), though P-value less dramatic\n\n* Keep both.\n- Expect to see a large effect of `age`, and a smaller one of `sex`.\n\n\n\n\n## Another way to build model\n\n\n* Start from model with everything and run `step`:\n\n\n::: {.cell}\n\n:::\n\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(brands.1, trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \ntrying - sex \n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\nmultinom(formula = brand ~ age + sex)\n\nCoefficients:\n (Intercept) age sexmale\n2 -11.25127 0.3682202 -0.5237736\n3 -22.25571 0.6859149 -0.4658215\n\nResidual Deviance: 1405.941 \nAIC: 1417.941 \n```\n:::\n:::\n\n\\normalsize\n \n\n* Final model contains both `age` and `sex` so neither\ncould be removed.\n\n\n## Making predictions\n\nFind age 5-number summary, and the two sexes:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n brand sex age \n 1:207 female:466 Min. :24.0 \n 2:307 male :269 1st Qu.:32.0 \n 3:221 Median :32.0 \n Mean :32.9 \n 3rd Qu.:34.0 \n Max. :38.0 \n```\n:::\n:::\n\n\nSpace the ages out a bit for prediction (see over).\n\n\\normalsize\n\n## Combinations\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(age = c(24, 30, 33, 35, 38), \n sex = c(\"female\", \"male\"), model = brands.1)\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## The predictions\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(brands.1, newdata = new)) %>%\n select(group, estimate, age, sex) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n* Young males prefer brand 1, \nbut older males prefer brand 3.\n\n* Females similar, but like brand 1 less and\nbrand 2 more.\n\n- A clear `brand` effect, but the `sex` effect is less clear. \n\n## Making a plot\n\n- `plot_predictions` doesn't quite work\n- so don't draw, edit, *then* make graph:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(brands.1, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g\n```\n:::\n\n\n## The graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-16-1.png){width=960}\n:::\n:::\n\n \n\n## Digesting the plot\n\n\n* Brand vs.\\ age: younger people (of both genders) prefer brand\n1, but older people (of both genders) prefer brand 3. (Explains\nsignificant age effect.)\n\n* Brand vs.\\ sex: females (solid) like brand 1 less than males\n(dashed), like brand 2 more (for all ages). \n\n* Not much brand difference between genders (solid and dashed\nlines of same colours close), but enough to be significant.\n\n* Model didn't include interaction, so modelled effect of gender\non brand same for each age, modelled effect of age same for each\ngender. (See also later.) \n\n\n## Alternative data format\n\nSummarize all people of same brand preference, same sex, same age on one line of data file with frequency on end:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n```\n1 0 24 1\n1 0 26 2\n1 0 27 4\n1 0 28 4\n1 0 29 7\n1 0 30 3\n...\n```\n\nWhole data set in 65 lines not 735! But how?\n\n\n## Getting alternative data format\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n group_by(age, sex, brand) %>%\n summarize(Freq = n()) %>%\n ungroup() -> b\nb\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\n## Fitting models, almost the same\n\n\n* Just have to remember `weights` to incorporate\nfrequencies.\n\n* Otherwise `multinom` assumes you have just 1 obs\non each line!\n\n* Again turn (numerical) `sex` and `brand` into factors:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nb %>%\n mutate(sex = factor(sex)) %>%\n mutate(brand = factor(brand)) -> bf\nb.1 <- multinom(brand ~ age + sex, data = bf, weights = Freq)\nb.2 <- multinom(brand ~ age, data = bf, weights = Freq)\n```\n:::\n\n\\normalsize\n\n\n\n## P-value for `sex` identical\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(b.2, b.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nSame P-value as before, so we haven't changed anything important.\n\n\n\n\n## Trying interaction between age and gender\n\n\n::: {.cell}\n\n:::\n\n\n \n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.4 <- update(brands.1, . ~ . + age:sex)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 15 (8 variable)\ninitial value 807.480032 \niter 10 value 703.191146\niter 20 value 702.572260\niter 30 value 702.570900\niter 30 value 702.570893\niter 30 value 702.570893\nfinal value 702.570893 \nconverged\n```\n:::\n\n```{.r .cell-code}\nanova(brands.1, brands.4)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \n\\normalsize\n\n* No evidence that effect of age on brand preference differs for\nthe two genders.\n\n## Make graph again\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(brands.4, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g4\n```\n:::\n\n\n\n## Not much difference in the graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng4\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-19-1.png){width=960}\n:::\n:::\n\n\n## Compare model without interaction\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-revealjs/unnamed-chunk-20-1.png){width=960}\n:::\n:::\n", "supporting": [ "logistic_files/figure-revealjs" ], diff --git a/_freeze/logistic/execute-results/tex.json b/_freeze/logistic/execute-results/tex.json index d733b5f..3b008bf 100644 --- a/_freeze/logistic/execute-results/tex.json +++ b/_freeze/logistic/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "d00d4260f0342837f54c858773a0870d", + "hash": "e6b7540e15c08007b53f23b153419a27", "result": { - "markdown": "---\ntitle: \"Logistic Regression\"\n---\n\n\n\n\n## Logistic regression\n\n\n* When response variable is measured/counted, regression can work well.\n\n* But what if response is yes/no, lived/died, success/failure?\n\n* Model *probability* of success.\n\n* Probability must be between 0 and 1; need method that ensures this.\n\n* *Logistic regression* does this. In R, is a\n*generalized linear model* with binomial \"family\": \n\n\n::: {.cell}\n\n```{.r .cell-code}\nglm(y ~ x, family=\"binomial\")\n```\n:::\n\n\n\n\n* Begin with simplest case.\n\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(marginaleffects)\nlibrary(broom)\nlibrary(nnet)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\nconflict_prefer(\"filter\", \"dplyr\")\nconflict_prefer(\"rename\", \"dplyr\")\nconflict_prefer(\"summarize\", \"dplyr\")\n```\n:::\n\n\n\n \n\n\n## The rats, part 1\n\n\n* Rats given dose of some poison; either live or die:\n\n\\small\n```\ndose status\n0 lived\n1 died\n2 lived\n3 lived\n4 died\n5 died\n```\n\n\\normalsize\n\n## Read in: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat.txt\"\nrats <- read_delim(my_url, \" \")\nrats\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n dose status\n \n1 0 lived \n2 1 died \n3 2 lived \n4 3 lived \n5 4 died \n6 5 died \n```\n:::\n:::\n\n\n \n\n\n## Basic logistic regression\n\n\n* Make response into a factor first:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats2 <- rats %>% mutate(status = factor(status))\n```\n:::\n\n\n\\normalsize\n \n\n\n* then fit model:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstatus.1 <- glm(status ~ dose, family = \"binomial\", data = rats2)\n```\n:::\n\n\n\\normalsize\n \n\n \n\n\n## Output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(status.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = status ~ dose, family = \"binomial\", data = rats2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 1.6841 1.7979 0.937 0.349\ndose -0.6736 0.6140 -1.097 0.273\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 8.3178 on 5 degrees of freedom\nResidual deviance: 6.7728 on 4 degrees of freedom\nAIC: 10.773\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Interpreting the output\n\n\n* Like (multiple) regression, get\ntests of significance of individual $x$'s\n\n* Here not significant (only 6 observations).\n\n* \"Slope\" for dose is negative, meaning that as dose increases, probability of event modelled (survival) decreases.\n\n\n\n\n\n## Output part 2: predicted survival probs\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(status.1)) %>% \n select(dose, estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n dose estimate\n1 0 0.8434490\n2 1 0.7331122\n3 2 0.5834187\n4 3 0.4165813\n5 4 0.2668878\n6 5 0.1565510\n```\n:::\n:::\n\n\n\\normalsize \n\n## On a graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(status.1, condition = \"dose\")\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-1-1.pdf)\n:::\n:::\n\n\n\n\n## The rats, more\n\n\n* More realistic: more rats at each dose (say 10).\n\n* Listing each rat on one line makes a big data file.\n\n* Use format below: dose, number of survivals, number of deaths.\n\n```\n\ndose lived died\n0 10 0\n1 7 3 \n2 6 4 \n3 4 6 \n4 2 8 \n5 1 9 \n\n```\n\n\n* 6 lines of data correspond to 60 actual rats.\n\n* Saved in `rat2.txt`.\n\n\n\n## These data\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat2.txt\"\nrat2 <- read_delim(my_url, \" \")\nrat2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n dose lived died\n \n1 0 10 0\n2 1 7 3\n3 2 6 4\n4 3 4 6\n5 4 2 8\n6 5 1 9\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Create response matrix:\n\n- Each row contains *multiple* observations.\n- Create *two-column* response:\n - \\#survivals in first column, \n - \\#deaths in second.\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nresponse <- with(rat2, cbind(lived, died))\nresponse\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n lived died\n[1,] 10 0\n[2,] 7 3\n[3,] 6 4\n[4,] 4 6\n[5,] 2 8\n[6,] 1 9\n```\n:::\n:::\n\n\n\\normalsize\n\n- Response is R `matrix`:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nclass(response)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"matrix\" \"array\" \n```\n:::\n:::\n\n\n\\normalsize\n\n \n## Fit logistic regression\n\n- using response you just made:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrat2.1 <- glm(response ~ dose,\n family = \"binomial\",\n data = rat2\n)\n```\n:::\n\n\n\n\n\n\n## Output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(rat2.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = response ~ dose, family = \"binomial\", data = rat2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 2.3619 0.6719 3.515 0.000439 ***\ndose -0.9448 0.2351 -4.018 5.87e-05 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 27.530 on 5 degrees of freedom\nResidual deviance: 2.474 on 4 degrees of freedom\nAIC: 18.94\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Predicted survival probs\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = rat2.1, dose = 0:5)\ncbind(predictions(rat2.1, newdata = new))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n rowid estimate p.value s.value conf.low conf.high dose\n1 1 0.9138762 0.0004389651 11.153606 0.73983042 0.9753671 0\n2 2 0.8048905 0.0031438277 8.313262 0.61695841 0.9135390 1\n3 3 0.6159474 0.1721141940 2.538562 0.44876099 0.7595916 2\n4 4 0.3840526 0.1721142921 2.538561 0.24040837 0.5512390 3\n5 5 0.1951095 0.0031438384 8.313257 0.08646093 0.3830417 4\n6 6 0.0861238 0.0004389668 11.153600 0.02463288 0.2601697 5\n```\n:::\n:::\n\n\n \n\n## On a picture\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(rat2.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, dose) %>% \n ggplot(aes(x = dose, y = estimate, \n ymin = conf.low, ymax = conf.high)) + \n geom_line() + geom_ribbon(alpha = 0.3)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-2-1.pdf)\n:::\n:::\n\n\n\n\n## Comments\n\n\n\n* Significant effect of dose. \n\n* Effect of larger dose is to *decrease* survival probability\n(\"slope\" negative; also see in decreasing predictions.)\n\n* Confidence intervals around prediction narrower (more data).\n\n## Multiple logistic regression\n\n\n* With more than one $x$, works much like multiple regression.\n\n* Example: study of patients with blood poisoning severe enough to warrant surgery. Relate survival to other potential risk factors.\n\n* Variables, 1=present, 0=absent:\n\n\n * survival (death from sepsis=1), response\n * shock\n * malnutrition\n * alcoholism\n * age (as numerical variable)\n * bowel infarction\n\n\n* See what relates to death.\n\n\n\n## Read in data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/sepsis.txt\"\nsepsis <- read_delim(my_url, \" \")\n```\n:::\n\n\n \n## Make sure categoricals really are\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% \n mutate(across(-age, \\(x) factor(x))) -> sepsis\n```\n:::\n\n\n\n\n## The data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 106 x 6\n death shock malnut alcohol age bowelinf\n \n 1 0 0 0 0 56 0 \n 2 0 0 0 0 80 0 \n 3 0 0 0 0 61 0 \n 4 0 0 0 0 26 0 \n 5 0 0 0 0 53 0 \n 6 1 0 1 0 87 0 \n 7 0 0 0 0 21 0 \n 8 1 0 0 1 69 0 \n 9 0 0 0 0 57 0 \n10 0 0 1 0 76 0 \n# i 96 more rows\n```\n:::\n:::\n\n\n\n \n\n\n## Fit model\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.1 <- glm(death ~ shock + malnut + alcohol + age +\n bowelinf,\nfamily = \"binomial\",\ndata = sepsis\n)\n```\n:::\n\n\n\n \n\n\n## Output part 1\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sepsis.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -9.75 2.54 -3.84 0.000124\n2 shock1 3.67 1.16 3.15 0.00161 \n3 malnut1 1.22 0.728 1.67 0.0948 \n4 alcohol1 3.35 0.982 3.42 0.000635\n5 age 0.0922 0.0303 3.04 0.00237 \n6 bowelinf1 2.80 1.16 2.40 0.0162 \n```\n:::\n:::\n\n\n \n\n\n\n* All P-values fairly small\n\n* but `malnut` not significant: remove.\n\n\n\n## Removing `malnut`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 <- update(sepsis.1, . ~ . - malnut)\ntidy(sepsis.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -8.89 2.32 -3.84 0.000124\n2 shock1 3.70 1.10 3.35 0.000797\n3 alcohol1 3.19 0.917 3.47 0.000514\n4 age 0.0898 0.0292 3.07 0.00211 \n5 bowelinf1 2.39 1.07 2.23 0.0260 \n```\n:::\n:::\n\n\n \n\n\n\n* Everything significant now.\n\n\n\n## Comments\n\n\n* Most of the original $x$'s helped predict death. Only `malnut` seemed not to add anything.\n\n* Removed `malnut` and tried again.\n\n* Everything remaining is significant (though `bowelinf`\nactually became *less* significant).\n\n* All coefficients are *positive*, so having any of the risk\nfactors (or being older)\n*increases* risk of death. \n\n\n## Predictions from model without \"malnut\"\n\n\n* A few (rows of original dataframe) chosen \"at random\":\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% slice(c(4, 1, 2, 11, 32)) -> new\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 6\n death shock malnut alcohol age bowelinf\n \n1 0 0 0 0 26 0 \n2 0 0 0 0 56 0 \n3 0 0 0 0 80 0 \n4 1 0 0 1 66 1 \n5 1 0 0 1 49 0 \n```\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, conf.low, conf.high, shock:bowelinf)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate conf.low conf.high shock malnut alcohol age bowelinf\n1 0.001415347 6.272642e-05 0.03103047 0 0 0 26 0\n2 0.020552383 4.102504e-03 0.09656596 0 0 0 56 0\n3 0.153416834 5.606838e-02 0.35603441 0 0 0 80 0\n4 0.931290137 5.490986e-01 0.99341482 0 0 1 66 1\n5 0.213000997 7.639063e-02 0.46967947 0 0 1 49 0\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Comments \n\n* Survival chances pretty good if no risk factors, though decreasing with age.\n\n* Having more than one risk factor reduces survival chances dramatically.\n\n* Usually good job of predicting survival; sometimes death predicted to survive.\n\n## Another way to assess effects\n\nof `age`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sepsis.2, age = seq(30, 70, 10))\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n death shock alcohol bowelinf age\n1 0 0 0 0 30\n2 0 0 0 0 40\n3 0 0 0 0 50\n4 0 0 0 0 60\n5 0 0 0 0 70\n```\n:::\n:::\n\n\n\n\n## Assessing age effect \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, shock:age)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate shock alcohol bowelinf age\n1 0.002026053 0 0 0 30\n2 0.004960283 0 0 0 40\n3 0.012092515 0 0 0 50\n4 0.029179226 0 0 0 60\n5 0.068729752 0 0 0 70\n```\n:::\n:::\n\n\n\n## Assessing shock effect\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(shock = c(0, 1), model = sepsis.2)\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n death alcohol age bowelinf shock\n1 0 0 51.28302 0 0\n2 0 0 51.28302 0 1\n```\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, death:shock)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate death alcohol age bowelinf shock\n1 0.01354973 0 0 51.28302 0 0\n2 0.35742607 0 0 51.28302 0 1\n```\n:::\n:::\n\n\n\\normalsize\n\n## Assessing proportionality of odds for age\n\n\n* An assumption we made is that log-odds of survival depends\nlinearly on age.\n\n* Hard to get your head around, but \nbasic idea is that survival chances go continuously up (or down)\nwith age, instead of (for example) going up and then down.\n\n* In this case, seems reasonable, but should check:\n\n\n## Residuals vs.\\ age\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 %>% augment(sepsis) %>% \n ggplot(aes(x = age, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/virtusentella-1.pdf)\n:::\n:::\n\n\n \n## Comments\n\n* No apparent problems overall.\n\n* Confusing \"line\" across: no risk factors, survived. \n\n\n\n## Probability and odds\n\n* For probability $p$, odds is $p/(1-p)$:\n\n\n \\begin{tabular}{rrrl}\n \\hline\n Prob.\\ & Odds & log-odds & in words\\\\\n \\hline\n 0.5 & $0.5/0.5=1/1=1.00$ & $0.00$ & ``even money''\\\\\n 0.1 & $0.1/0.9=1/9=0.11$ & $-2.20$ & ``9 to 1''\\\\\n 0.4 & $0.4/0.6=1/1.5=0.67$ & $-0.41$ & ``1.5 to 1''\\\\\n 0.8 & $0.8/0.2=4/1=4.00$ & $1.39$ & ``4 to 1 on''\\\\\n \\hline\n \\end{tabular}\n\n\n* Gamblers use odds: if you win at 9 to 1 odds, get original\nstake back plus 9 times the stake.\n\n* Probability has to be between 0 and 1\n\n* Odds between 0 and infinity\n\n* *Log*-odds can be anything: any log-odds corresponds to\nvalid probability.\n\n\n\n## Odds ratio\n\n\n* Suppose 90 of 100 men drank wine last week, but only 20 of 100 women.\n\n* Prob of man drinking wine $90/100=0.9$, woman $20/100=0.2$.\n\n* Odds of man drinking wine $0.9/0.1=9$, woman $0.2/0.8=0.25$.\n\n* Ratio of odds is $9/0.25=36$.\n\n* Way of quantifying difference between men and women: ``odds of\ndrinking wine 36 times larger for males than females''. \n\n\n\n## Sepsis data again\n\n\n* Recall prediction of probability of death from risk factors:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy <- tidy(sepsis.2)\nsepsis.2.tidy\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -8.89 2.32 -3.84 0.000124\n2 shock1 3.70 1.10 3.35 0.000797\n3 alcohol1 3.19 0.917 3.47 0.000514\n4 age 0.0898 0.0292 3.07 0.00211 \n5 bowelinf1 2.39 1.07 2.23 0.0260 \n```\n:::\n:::\n\n\n\n \n\n\n* Slopes in column `estimate`.\n\n\n\n## Multiplying the odds\n\n\n* Can interpret slopes by taking \"exp\" of them. We ignore intercept.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy %>% \n mutate(exp_coeff=exp(estimate)) %>% \n select(term, exp_coeff)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 2\n term exp_coeff\n \n1 (Intercept) 0.000137\n2 shock1 40.5 \n3 alcohol1 24.2 \n4 age 1.09 \n5 bowelinf1 10.9 \n```\n:::\n:::\n\n\n\n## Interpretation\n\n\\small\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 2\n term exp_coeff\n \n1 (Intercept) 0.000137\n2 shock1 40.5 \n3 alcohol1 24.2 \n4 age 1.09 \n5 bowelinf1 10.9 \n```\n:::\n:::\n\n\n\\normalsize\n\n\n* These say \"how much do you *multiply* odds of death by\nfor increase of 1 in corresponding risk factor?\" Or, what is odds\nratio for that factor being 1 (present) vs.\\ 0 (absent)?\n\n* Eg.\\ being alcoholic vs.\\ not increases odds of death by 24 times\n\n* One year older multiplies odds by about 1.1 times. Over 40 years,\nabout $1.09^{40}=31$ times. \n\n\n\n## Odds ratio and relative risk\n\n\n* **Relative risk** is ratio of probabilities.\n\n* Above: 90 of 100 men (0.9) drank wine, 20 of 100 women (0.2).\n\n* Relative risk 0.9/0.2=4.5. (odds ratio was 36).\n\n* When probabilities small, relative risk and odds ratio similar.\n\n* Eg.\\ prob of man having disease 0.02, woman 0.01.\n\n* Relative risk $0.02/0.01=2$.\n\n## Odds ratio vs.\\ relative risk\n\n- Odds for men and for women:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(od1 <- 0.02 / 0.98) # men\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.02040816\n```\n:::\n\n```{.r .cell-code}\n(od2 <- 0.01 / 0.99) # women\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.01010101\n```\n:::\n:::\n\n\n\n- Odds ratio \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nod1 / od2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.020408\n```\n:::\n:::\n\n\n\n\n- Very close to relative risk of 2.\n\n\n## More than 2 response categories\n\n\n* With 2 response categories, model the probability of one, and prob of other is one minus that. So doesn't matter which category you model.\n\n* With more than 2 categories, have to think more carefully about the categories: are they\n\n\n* *ordered*: you can put them in a natural order (like low, medium, high)\n\n* *nominal*: ordering the categories doesn't make sense (like red, green, blue).\n\n\n* R handles both kinds of response; learn how.\n\n\n\n## Ordinal response: the miners\n\n\n* \nModel probability of being in given category *or lower*.\n\n* Example: coal-miners often suffer disease pneumoconiosis. Likelihood of disease believed to be greater \namong miners who have worked longer. \n\n* Severity of disease measured on categorical scale: none,\nmoderate, severe.\n\n## Miners data\n\n* Data are frequencies:\n\n```\nExposure None Moderate Severe\n5.8 98 0 0\n15.0 51 2 1\n21.5 34 6 3\n27.5 35 5 8\n33.5 32 10 9\n39.5 23 7 8\n46.0 12 6 10\n51.5 4 2 5\n```\n\n\n\n\n## Reading the data\n\nData in aligned columns with more than one space between, so: \n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/miners-tab.txt\"\nfreqs <- read_table(my_url)\n```\n:::\n\n\n\\normalsize\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Exposure None Moderate Severe\n \n1 5.8 98 0 0\n2 15 51 2 1\n3 21.5 34 6 3\n4 27.5 35 5 8\n5 33.5 32 10 9\n6 39.5 23 7 8\n7 46 12 6 10\n8 51.5 4 2 5\n```\n:::\n:::\n\n\n\n \n\n\n## Tidying \n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>%\n pivot_longer(-Exposure, names_to = \"Severity\", values_to = \"Freq\") %>%\n mutate(Severity = fct_inorder(Severity)) -> miners\n```\n:::\n\n\n\n \n\n\n## Result\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 24 x 3\n Exposure Severity Freq\n \n 1 5.8 None 98\n 2 5.8 Moderate 0\n 3 5.8 Severe 0\n 4 15 None 51\n 5 15 Moderate 2\n 6 15 Severe 1\n 7 21.5 None 34\n 8 21.5 Moderate 6\n 9 21.5 Severe 3\n10 27.5 None 35\n# i 14 more rows\n```\n:::\n:::\n\n\n\\normalsize\n \n\n\n## Plot proportions against exposure\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nminers %>% \n group_by(Exposure) %>% \n mutate(proportion = Freq / sum(Freq)) -> prop\nggplot(prop, aes(x = Exposure, y = proportion,\n colour = Severity)) + \n geom_point() + geom_smooth(se = F)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/bLogistic-28-1.pdf)\n:::\n:::\n\n\n\\normalsize\n\n\n## Reminder of data setup\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 24 x 3\n Exposure Severity Freq\n \n 1 5.8 None 98\n 2 5.8 Moderate 0\n 3 5.8 Severe 0\n 4 15 None 51\n 5 15 Moderate 2\n 6 15 Severe 1\n 7 21.5 None 34\n 8 21.5 Moderate 6\n 9 21.5 Severe 3\n10 27.5 None 35\n# i 14 more rows\n```\n:::\n:::\n\n\n\n \n\\normalsize\n\n\n\n## Fitting ordered logistic model\n\nUse function `polr` from package `MASS`. Like `glm`.\n\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners\n)\n```\n:::\n\n\n \n\n\n## Output: not very illuminating\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners,\n Hess = TRUE\n)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sev.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\npolr(formula = Severity ~ Exposure, data = miners, weights = Freq, \n Hess = TRUE)\n\nCoefficients:\n Value Std. Error t value\nExposure 0.0959 0.01194 8.034\n\nIntercepts:\n Value Std. Error t value\nNone|Moderate 3.9558 0.4097 9.6558\nModerate|Severe 4.8690 0.4411 11.0383\n\nResidual Deviance: 416.9188 \nAIC: 422.9188 \n```\n:::\n:::\n\n\n\\normalsize\n \n\n## Does exposure have an effect?\nFit model without `Exposure`, and compare\nusing `anova`. Note `1` for model with just intercept:\n\n\n\n::: {.cell}\n\n:::\n\n\n\n \n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.0 <- polr(Severity ~ 1, weights = Freq, data = miners)\nanova(sev.0, sev.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of ordinal regression models\n\nResponse: Severity\n Model Resid. df Resid. Dev Test Df LR stat.\n1 1 369 505.1621 \n2 Exposure 368 416.9188 1 vs 2 1 88.24324\n Pr(Chi)\n1 \n2 0\n```\n:::\n:::\n\n\n\\normalsize\n\nExposure definitely has effect on severity of disease. \n\n\n## Another way\n\n\n* What (if anything) can we drop from model with `exposure`?\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(sev.1, test = \"Chisq\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nSingle term deletions\n\nModel:\nSeverity ~ Exposure\n Df AIC LRT Pr(>Chi) \n 422.92 \nExposure 1 509.16 88.243 < 2.2e-16 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n \n\n\n* Nothing. Exposure definitely has effect.\n\n\n\n## Predicted probabilities 1/2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>% select(Exposure) -> new\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 1\n Exposure\n \n1 5.8\n2 15 \n3 21.5\n4 27.5\n5 33.5\n6 39.5\n7 46 \n8 51.5\n```\n:::\n:::\n\n\n\n\n## Predicted probabilities 2/2\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sev.1, newdata = new)) %>%\n select(group, estimate, Exposure) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Exposure None Moderate Severe\n \n1 5.8 0.968 0.0191 0.0132\n2 15 0.925 0.0433 0.0314\n3 21.5 0.869 0.0739 0.0569\n4 27.5 0.789 0.114 0.0969\n5 33.5 0.678 0.162 0.160 \n6 39.5 0.542 0.205 0.253 \n7 46 0.388 0.224 0.388 \n8 51.5 0.272 0.210 0.517 \n```\n:::\n:::\n\n\n\\normalsize\n\n## Plot of predicted probabilities\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(model = sev.1, condition = c(\"Exposure\", \"group\"),\n type = \"probs\") +\n geom_point(data = prop, aes(x = Exposure, y = proportion, \n colour = Severity)) -> ggg\n```\n:::\n\n\n\n## The graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggg\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-11-1.pdf)\n:::\n:::\n\n\n\n\n## Comments\n\n\n* Model appears to match data well enough.\n\n- As exposure goes up, prob of None\ngoes down, Severe goes up (sharply for high exposure).\n\n- So more exposure means worse disease.\n\n\n## Unordered responses\n\n\n* With unordered (nominal) responses, can use *generalized logit*.\n\n* Example: 735 people, record age and sex (male 0, female 1), which of 3 brands of some product preferred.\n\n* Data in `mlogit.csv` separated by commas (so\n`read_csv` will work):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/mlogit.csv\"\nbrandpref <- read_csv(my_url)\n```\n:::\n\n\n \n\n\n\n\n## The data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 735 x 3\n brand sex age\n \n 1 1 0 24\n 2 1 0 26\n 3 1 0 26\n 4 1 1 27\n 5 1 1 27\n 6 3 1 27\n 7 1 0 27\n 8 1 0 27\n 9 1 1 27\n10 1 0 27\n# i 725 more rows\n```\n:::\n:::\n\n\n\n \n\n\n## Bashing into shape\n\n\n* `sex` and `brand` not meaningful as numbers, so\nturn into factors:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n mutate(sex = ifelse(sex == 1, \"female\", \"male\"), \n sex = factor(sex),\n brand = factor(brand)\n ) -> brandpref\n```\n:::\n\n\n \n \n## Fitting model \n\n* We use `multinom` from package `nnet`. Works\nlike `polr`.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.1 <- multinom(brand ~ age + sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 12 (6 variable)\ninitial value 807.480032 \niter 10 value 702.990572\nfinal value 702.970704 \nconverged\n```\n:::\n:::\n\n\n \n\n\n## Can we drop anything?\n\n\n* Unfortunately `drop1` seems not to work:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(brands.1, test = \"Chisq\", trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \n```\n:::\n\n::: {.cell-output .cell-output-error}\n```\nError in if (trace) {: argument is not interpretable as logical\n```\n:::\n:::\n\n\n\n* So, fall back on fitting model without what you want to test, and\ncomparing using `anova`. \n\n\n\n## Do age/sex help predict brand? 1/3\n\nFit models without each of age and sex:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.2 <- multinom(brand ~ age, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \niter 10 value 706.796323\niter 10 value 706.796322\nfinal value 706.796322 \nconverged\n```\n:::\n\n```{.r .cell-code}\nbrands.3 <- multinom(brand ~ sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \nfinal value 791.861266 \nconverged\n```\n:::\n:::\n\n\n \n\n\n## Do age/sex help predict brand? 2/3\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(brands.2, brands.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df LR stat.\n1 age 1466 1413.593 \n2 age + sex 1464 1405.941 1 vs 2 2 7.651236\n Pr(Chi)\n1 \n2 0.02180496\n```\n:::\n\n```{.r .cell-code}\nanova(brands.3, brands.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df LR stat.\n1 sex 1466 1583.723 \n2 age + sex 1464 1405.941 1 vs 2 2 177.7811\n Pr(Chi)\n1 \n2 0\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Do age/sex help predict brand? 3/3\n\n\n* `age` definitely significant (second `anova`)\n\n* `sex` significant also (first `anova`), though P-value less dramatic\n\n* Keep both.\n- Expect to see a large effect of `age`, and a smaller one of `sex`.\n\n\n\n\n## Another way to build model\n\n\n* Start from model with everything and run `step`:\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(brands.1, trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \ntrying - sex \n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\nmultinom(formula = brand ~ age + sex)\n\nCoefficients:\n (Intercept) age sexmale\n2 -11.25127 0.3682202 -0.5237736\n3 -22.25571 0.6859149 -0.4658215\n\nResidual Deviance: 1405.941 \nAIC: 1417.941 \n```\n:::\n:::\n\n\n\\normalsize\n \n\n* Final model contains both `age` and `sex` so neither\ncould be removed.\n\n\n## Making predictions\n\nFind age 5-number summary, and the two sexes:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n brand sex age \n 1:207 female:466 Min. :24.0 \n 2:307 male :269 1st Qu.:32.0 \n 3:221 Median :32.0 \n Mean :32.9 \n 3rd Qu.:34.0 \n Max. :38.0 \n```\n:::\n:::\n\n\n\nSpace the ages out a bit for prediction (see over).\n\n\\normalsize\n\n## Combinations\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(age = c(24, 30, 33, 35, 38), \n sex = c(\"female\", \"male\"), model = brands.1)\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n brand age sex\n1 2 24 female\n2 2 24 male\n3 2 30 female\n4 2 30 male\n5 2 33 female\n6 2 33 male\n7 2 35 female\n8 2 35 male\n9 2 38 female\n10 2 38 male\n```\n:::\n:::\n\n\n\n## The predictions\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(brands.1, newdata = new)) %>%\n select(group, estimate, age, sex) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 5\n age sex `1` `2` `3`\n \n 1 24 female 0.915 0.0819 0.00279\n 2 24 male 0.948 0.0502 0.00181\n 3 30 female 0.500 0.407 0.0933 \n 4 30 male 0.625 0.302 0.0732 \n 5 33 female 0.203 0.500 0.297 \n 6 33 male 0.296 0.432 0.272 \n 7 35 female 0.0840 0.432 0.484 \n 8 35 male 0.131 0.397 0.472 \n 9 38 female 0.0162 0.252 0.732 \n10 38 male 0.0260 0.239 0.735 \n```\n:::\n:::\n\n\n\n## Comments\n\n* Young males prefer brand 1, \nbut older males prefer brand 3.\n\n* Females similar, but like brand 1 less and\nbrand 2 more.\n\n- A clear `brand` effect, but the `sex` effect is less clear. \n\n## Making a plot\n\n- `plot_cap` doesn't quite work\n- so don't draw, edit, *then* make graph:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(brands.1, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g\n```\n:::\n\n\n\n## The graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-16-1.pdf)\n:::\n:::\n\n\n \n\n## Digesting the plot\n\n\n* Brand vs.\\ age: younger people (of both genders) prefer brand\n1, but older people (of both genders) prefer brand 3. (Explains\nsignificant age effect.)\n\n* Brand vs.\\ sex: females (solid) like brand 1 less than males\n(dashed), like brand 2 more (for all ages). \n\n* Not much brand difference between genders (solid and dashed\nlines of same colours close), but enough to be significant.\n\n* Model didn't include interaction, so modelled effect of gender\non brand same for each age, modelled effect of age same for each\ngender. (See also later.) \n\n\n## Alternative data format\n\nSummarize all people of same brand preference, same sex, same age on one line of data file with frequency on end:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 735 x 3\n brand sex age\n \n 1 1 male 24\n 2 1 male 26\n 3 1 male 26\n 4 1 female 27\n 5 1 female 27\n 6 3 female 27\n 7 1 male 27\n 8 1 male 27\n 9 1 female 27\n10 1 male 27\n# i 725 more rows\n```\n:::\n:::\n\n\n\n\n```\n1 0 24 1\n1 0 26 2\n1 0 27 4\n1 0 28 4\n1 0 29 7\n1 0 30 3\n...\n```\n\nWhole data set in 65 lines not 735! But how?\n\n\n## Getting alternative data format\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n group_by(age, sex, brand) %>%\n summarize(Freq = n()) %>%\n ungroup() -> b\nb\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 65 x 4\n age sex brand Freq\n \n 1 24 male 1 1\n 2 26 male 1 2\n 3 27 female 1 4\n 4 27 female 3 1\n 5 27 male 1 4\n 6 28 female 1 6\n 7 28 female 2 2\n 8 28 female 3 1\n 9 28 male 1 4\n10 28 male 3 2\n# i 55 more rows\n```\n:::\n:::\n\n\n\n \n\n## Fitting models, almost the same\n\n\n* Just have to remember `weights` to incorporate\nfrequencies.\n\n* Otherwise `multinom` assumes you have just 1 obs\non each line!\n\n* Again turn (numerical) `sex` and `brand` into factors:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nb %>%\n mutate(sex = factor(sex)) %>%\n mutate(brand = factor(brand)) -> bf\nb.1 <- multinom(brand ~ age + sex, data = bf, weights = Freq)\nb.2 <- multinom(brand ~ age, data = bf, weights = Freq)\n```\n:::\n\n\n\\normalsize\n\n\n\n## P-value for `sex` identical\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(b.2, b.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df LR stat.\n1 age 126 1413.593 \n2 age + sex 124 1405.941 1 vs 2 2 7.651236\n Pr(Chi)\n1 \n2 0.02180496\n```\n:::\n:::\n\n\n\\normalsize\n\nSame P-value as before, so we haven't changed anything important.\n\n\n\n\n## Trying interaction between age and gender\n\n\n\n::: {.cell}\n\n:::\n\n\n\n \n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.4 <- update(brands.1, . ~ . + age:sex)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 15 (8 variable)\ninitial value 807.480032 \niter 10 value 703.191146\niter 20 value 702.572260\niter 30 value 702.570900\niter 30 value 702.570893\niter 30 value 702.570893\nfinal value 702.570893 \nconverged\n```\n:::\n\n```{.r .cell-code}\nanova(brands.1, brands.4)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df\n1 age + sex 1464 1405.941 \n2 age + sex + age:sex 1462 1405.142 1 vs 2 2\n LR stat. Pr(Chi)\n1 \n2 0.7996223 0.6704466\n```\n:::\n:::\n\n\n\n \n\\normalsize\n\n* No evidence that effect of age on brand preference differs for\nthe two genders.\n\n## Make graph again\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_cap(brands.4, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g4\n```\n:::\n\n\n\n\n## Not much difference in the graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng4\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-19-1.pdf)\n:::\n:::\n\n\n\n## Compare model without interaction\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-20-1.pdf)\n:::\n:::\n", + "markdown": "---\ntitle: \"Logistic Regression\"\n---\n\n\n\n\n## Logistic regression\n\n\n* When response variable is measured/counted, regression can work well.\n\n* But what if response is yes/no, lived/died, success/failure?\n\n* Model *probability* of success.\n\n* Probability must be between 0 and 1; need method that ensures this.\n\n* *Logistic regression* does this. In R, is a\n*generalized linear model* with binomial \"family\": \n\n\n::: {.cell}\n\n```{.r .cell-code}\nglm(y ~ x, family=\"binomial\")\n```\n:::\n\n\n\n\n* Begin with simplest case.\n\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS)\nlibrary(tidyverse)\nlibrary(marginaleffects)\nlibrary(broom)\nlibrary(nnet)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\nconflict_prefer(\"filter\", \"dplyr\")\nconflict_prefer(\"rename\", \"dplyr\")\nconflict_prefer(\"summarize\", \"dplyr\")\n```\n:::\n\n\n\n \n\n\n## The rats, part 1\n\n\n* Rats given dose of some poison; either live or die:\n\n\\small\n```\ndose status\n0 lived\n1 died\n2 lived\n3 lived\n4 died\n5 died\n```\n\n\\normalsize\n\n## Read in: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat.txt\"\nrats <- read_delim(my_url, \" \")\nrats\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n dose status\n \n1 0 lived \n2 1 died \n3 2 lived \n4 3 lived \n5 4 died \n6 5 died \n```\n:::\n:::\n\n\n \n\n\n## Basic logistic regression\n\n\n* Make response into a factor first:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrats2 <- rats %>% mutate(status = factor(status))\n```\n:::\n\n\n\\normalsize\n \n\n\n* then fit model:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstatus.1 <- glm(status ~ dose, family = \"binomial\", data = rats2)\n```\n:::\n\n\n\\normalsize\n \n\n \n\n\n## Output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(status.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = status ~ dose, family = \"binomial\", data = rats2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|)\n(Intercept) 1.6841 1.7979 0.937 0.349\ndose -0.6736 0.6140 -1.097 0.273\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 8.3178 on 5 degrees of freedom\nResidual deviance: 6.7728 on 4 degrees of freedom\nAIC: 10.773\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Interpreting the output\n\n\n* Like (multiple) regression, get\ntests of significance of individual $x$'s\n\n* Here not significant (only 6 observations).\n\n* \"Slope\" for dose is negative, meaning that as dose increases, probability of event modelled (survival) decreases.\n\n\n\n\n\n## Output part 2: predicted survival probs\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(status.1)) %>% \n select(dose, estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n dose estimate\n1 0 0.8434490\n2 1 0.7331122\n3 2 0.5834187\n4 3 0.4165813\n5 4 0.2668878\n6 5 0.1565510\n```\n:::\n:::\n\n\n\\normalsize \n\n## On a graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(status.1, condition = \"dose\")\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-1-1.pdf)\n:::\n:::\n\n\n\n\n## The rats, more\n\n\n* More realistic: more rats at each dose (say 10).\n\n* Listing each rat on one line makes a big data file.\n\n* Use format below: dose, number of survivals, number of deaths.\n\n```\n\ndose lived died\n0 10 0\n1 7 3 \n2 6 4 \n3 4 6 \n4 2 8 \n5 1 9 \n\n```\n\n\n* 6 lines of data correspond to 60 actual rats.\n\n* Saved in `rat2.txt`.\n\n\n\n## These data\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/rat2.txt\"\nrat2 <- read_delim(my_url, \" \")\nrat2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n dose lived died\n \n1 0 10 0\n2 1 7 3\n3 2 6 4\n4 3 4 6\n5 4 2 8\n6 5 1 9\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Create response matrix:\n\n- Each row contains *multiple* observations.\n- Create *two-column* response:\n - \\#survivals in first column, \n - \\#deaths in second.\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nresponse <- with(rat2, cbind(lived, died))\nresponse\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n lived died\n[1,] 10 0\n[2,] 7 3\n[3,] 6 4\n[4,] 4 6\n[5,] 2 8\n[6,] 1 9\n```\n:::\n:::\n\n\n\\normalsize\n\n- Response is R `matrix`:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nclass(response)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"matrix\" \"array\" \n```\n:::\n:::\n\n\n\\normalsize\n\n \n## Fit logistic regression\n\n- using response you just made:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrat2.1 <- glm(response ~ dose,\n family = \"binomial\",\n data = rat2\n)\n```\n:::\n\n\n\n\n\n\n## Output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(rat2.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nglm(formula = response ~ dose, family = \"binomial\", data = rat2)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 2.3619 0.6719 3.515 0.000439 ***\ndose -0.9448 0.2351 -4.018 5.87e-05 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for binomial family taken to be 1)\n\n Null deviance: 27.530 on 5 degrees of freedom\nResidual deviance: 2.474 on 4 degrees of freedom\nAIC: 18.94\n\nNumber of Fisher Scoring iterations: 4\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Predicted survival probs\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = rat2.1, dose = 0:5)\ncbind(predictions(rat2.1, newdata = new))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n rowid estimate p.value s.value conf.low conf.high dose\n1 1 0.9138762 0.0004389651 11.153606 0.73983042 0.9753671 0\n2 2 0.8048905 0.0031438277 8.313262 0.61695841 0.9135390 1\n3 3 0.6159474 0.1721141934 2.538562 0.44876099 0.7595916 2\n4 4 0.3840526 0.1721142946 2.538561 0.24040837 0.5512390 3\n5 5 0.1951095 0.0031438386 8.313257 0.08646093 0.3830417 4\n6 6 0.0861238 0.0004389668 11.153600 0.02463288 0.2601697 5\n```\n:::\n:::\n\n\n \n\n## On a picture\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(rat2.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, dose) %>% \n ggplot(aes(x = dose, y = estimate, \n ymin = conf.low, ymax = conf.high)) + \n geom_line() + geom_ribbon(alpha = 0.3)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-2-1.pdf)\n:::\n:::\n\n\n\n\n## Comments\n\n\n\n* Significant effect of dose. \n\n* Effect of larger dose is to *decrease* survival probability\n(\"slope\" negative; also see in decreasing predictions.)\n\n* Confidence intervals around prediction narrower (more data).\n\n## Multiple logistic regression\n\n\n* With more than one $x$, works much like multiple regression.\n\n* Example: study of patients with blood poisoning severe enough to warrant surgery. Relate survival to other potential risk factors.\n\n* Variables, 1=present, 0=absent:\n\n\n * survival (death from sepsis=1), response\n * shock\n * malnutrition\n * alcoholism\n * age (as numerical variable)\n * bowel infarction\n\n\n* See what relates to death.\n\n\n\n## Read in data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/sepsis.txt\"\nsepsis <- read_delim(my_url, \" \")\n```\n:::\n\n\n \n## Make sure categoricals really are\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% \n mutate(across(-age, \\(x) factor(x))) -> sepsis\n```\n:::\n\n\n\n\n## The data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 106 x 6\n death shock malnut alcohol age bowelinf\n \n 1 0 0 0 0 56 0 \n 2 0 0 0 0 80 0 \n 3 0 0 0 0 61 0 \n 4 0 0 0 0 26 0 \n 5 0 0 0 0 53 0 \n 6 1 0 1 0 87 0 \n 7 0 0 0 0 21 0 \n 8 1 0 0 1 69 0 \n 9 0 0 0 0 57 0 \n10 0 0 1 0 76 0 \n# i 96 more rows\n```\n:::\n:::\n\n\n\n \n\n\n## Fit model\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.1 <- glm(death ~ shock + malnut + alcohol + age +\n bowelinf,\nfamily = \"binomial\",\ndata = sepsis\n)\n```\n:::\n\n\n\n \n\n\n## Output part 1\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sepsis.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -9.75 2.54 -3.84 0.000124\n2 shock1 3.67 1.16 3.15 0.00161 \n3 malnut1 1.22 0.728 1.67 0.0948 \n4 alcohol1 3.35 0.982 3.42 0.000635\n5 age 0.0922 0.0303 3.04 0.00237 \n6 bowelinf1 2.80 1.16 2.40 0.0162 \n```\n:::\n:::\n\n\n \n\n\n\n* All P-values fairly small\n\n* but `malnut` not significant: remove.\n\n\n\n## Removing `malnut`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 <- update(sepsis.1, . ~ . - malnut)\ntidy(sepsis.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -8.89 2.32 -3.84 0.000124\n2 shock1 3.70 1.10 3.35 0.000797\n3 alcohol1 3.19 0.917 3.47 0.000514\n4 age 0.0898 0.0292 3.07 0.00211 \n5 bowelinf1 2.39 1.07 2.23 0.0260 \n```\n:::\n:::\n\n\n \n\n\n\n* Everything significant now.\n\n\n\n## Comments\n\n\n* Most of the original $x$'s helped predict death. Only `malnut` seemed not to add anything.\n\n* Removed `malnut` and tried again.\n\n* Everything remaining is significant (though `bowelinf`\nactually became *less* significant).\n\n* All coefficients are *positive*, so having any of the risk\nfactors (or being older)\n*increases* risk of death. \n\n\n## Predictions from model without \"malnut\"\n\n\n* A few (rows of original dataframe) chosen \"at random\":\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis %>% slice(c(4, 1, 2, 11, 32)) -> new\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 6\n death shock malnut alcohol age bowelinf\n \n1 0 0 0 0 26 0 \n2 0 0 0 0 56 0 \n3 0 0 0 0 80 0 \n4 1 0 0 1 66 1 \n5 1 0 0 1 49 0 \n```\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, conf.low, conf.high, shock:bowelinf)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate conf.low conf.high shock malnut alcohol age bowelinf\n1 0.001415347 6.272642e-05 0.03103047 0 0 0 26 0\n2 0.020552383 4.102504e-03 0.09656596 0 0 0 56 0\n3 0.153416834 5.606838e-02 0.35603441 0 0 0 80 0\n4 0.931290137 5.490986e-01 0.99341482 0 0 1 66 1\n5 0.213000997 7.639063e-02 0.46967947 0 0 1 49 0\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Comments \n\n* Survival chances pretty good if no risk factors, though decreasing with age.\n\n* Having more than one risk factor reduces survival chances dramatically.\n\n* Usually good job of predicting survival; sometimes death predicted to survive.\n\n## Another way to assess effects\n\nof `age`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sepsis.2, age = seq(30, 70, 10))\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n death shock alcohol bowelinf age\n1 0 0 0 0 30\n2 0 0 0 0 40\n3 0 0 0 0 50\n4 0 0 0 0 60\n5 0 0 0 0 70\n```\n:::\n:::\n\n\n\n\n## Assessing age effect \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, shock:age)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate shock alcohol bowelinf age\n1 0.002026053 0 0 0 30\n2 0.004960283 0 0 0 40\n3 0.012092515 0 0 0 50\n4 0.029179226 0 0 0 60\n5 0.068729752 0 0 0 70\n```\n:::\n:::\n\n\n\n## Assessing shock effect\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(shock = c(0, 1), model = sepsis.2)\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n death alcohol age bowelinf shock\n1 0 0 51.28302 0 0\n2 0 0 51.28302 0 1\n```\n:::\n\n```{.r .cell-code}\ncbind(predictions(sepsis.2, newdata = new)) %>% \n select(estimate, death:shock)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate death alcohol age bowelinf shock\n1 0.01354973 0 0 51.28302 0 0\n2 0.35742607 0 0 51.28302 0 1\n```\n:::\n:::\n\n\n\\normalsize\n\n## Assessing proportionality of odds for age\n\n\n* An assumption we made is that log-odds of survival depends\nlinearly on age.\n\n* Hard to get your head around, but \nbasic idea is that survival chances go continuously up (or down)\nwith age, instead of (for example) going up and then down.\n\n* In this case, seems reasonable, but should check:\n\n\n## Residuals vs.\\ age\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2 %>% augment(sepsis) %>% \n ggplot(aes(x = age, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/virtusentella-1.pdf)\n:::\n:::\n\n\n \n## Comments\n\n* No apparent problems overall.\n\n* Confusing \"line\" across: no risk factors, survived. \n\n\n\n## Probability and odds\n\n* For probability $p$, odds is $p/(1-p)$:\n\n\n \\begin{tabular}{rrrl}\n \\hline\n Prob.\\ & Odds & log-odds & in words\\\\\n \\hline\n 0.5 & $0.5/0.5=1/1=1.00$ & $0.00$ & ``even money''\\\\\n 0.1 & $0.1/0.9=1/9=0.11$ & $-2.20$ & ``9 to 1''\\\\\n 0.4 & $0.4/0.6=1/1.5=0.67$ & $-0.41$ & ``1.5 to 1''\\\\\n 0.8 & $0.8/0.2=4/1=4.00$ & $1.39$ & ``4 to 1 on''\\\\\n \\hline\n \\end{tabular}\n\n\n* Gamblers use odds: if you win at 9 to 1 odds, get original\nstake back plus 9 times the stake.\n\n* Probability has to be between 0 and 1\n\n* Odds between 0 and infinity\n\n* *Log*-odds can be anything: any log-odds corresponds to\nvalid probability.\n\n\n\n## Odds ratio\n\n\n* Suppose 90 of 100 men drank wine last week, but only 20 of 100 women.\n\n* Prob of man drinking wine $90/100=0.9$, woman $20/100=0.2$.\n\n* Odds of man drinking wine $0.9/0.1=9$, woman $0.2/0.8=0.25$.\n\n* Ratio of odds is $9/0.25=36$.\n\n* Way of quantifying difference between men and women: ``odds of\ndrinking wine 36 times larger for males than females''. \n\n\n\n## Sepsis data again\n\n\n* Recall prediction of probability of death from risk factors:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy <- tidy(sepsis.2)\nsepsis.2.tidy\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -8.89 2.32 -3.84 0.000124\n2 shock1 3.70 1.10 3.35 0.000797\n3 alcohol1 3.19 0.917 3.47 0.000514\n4 age 0.0898 0.0292 3.07 0.00211 \n5 bowelinf1 2.39 1.07 2.23 0.0260 \n```\n:::\n:::\n\n\n\n \n\n\n* Slopes in column `estimate`.\n\n\n\n## Multiplying the odds\n\n\n* Can interpret slopes by taking \"exp\" of them. We ignore intercept.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsepsis.2.tidy %>% \n mutate(exp_coeff=exp(estimate)) %>% \n select(term, exp_coeff)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 2\n term exp_coeff\n \n1 (Intercept) 0.000137\n2 shock1 40.5 \n3 alcohol1 24.2 \n4 age 1.09 \n5 bowelinf1 10.9 \n```\n:::\n:::\n\n\n\n## Interpretation\n\n\\small\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 2\n term exp_coeff\n \n1 (Intercept) 0.000137\n2 shock1 40.5 \n3 alcohol1 24.2 \n4 age 1.09 \n5 bowelinf1 10.9 \n```\n:::\n:::\n\n\n\\normalsize\n\n\n* These say \"how much do you *multiply* odds of death by\nfor increase of 1 in corresponding risk factor?\" Or, what is odds\nratio for that factor being 1 (present) vs.\\ 0 (absent)?\n\n* Eg.\\ being alcoholic vs.\\ not increases odds of death by 24 times\n\n* One year older multiplies odds by about 1.1 times. Over 40 years,\nabout $1.09^{40}=31$ times. \n\n\n\n## Odds ratio and relative risk\n\n\n* **Relative risk** is ratio of probabilities.\n\n* Above: 90 of 100 men (0.9) drank wine, 20 of 100 women (0.2).\n\n* Relative risk 0.9/0.2=4.5. (odds ratio was 36).\n\n* When probabilities small, relative risk and odds ratio similar.\n\n* Eg.\\ prob of man having disease 0.02, woman 0.01.\n\n* Relative risk $0.02/0.01=2$.\n\n## Odds ratio vs.\\ relative risk\n\n- Odds for men and for women:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(od1 <- 0.02 / 0.98) # men\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.02040816\n```\n:::\n\n```{.r .cell-code}\n(od2 <- 0.01 / 0.99) # women\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.01010101\n```\n:::\n:::\n\n\n\n- Odds ratio \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nod1 / od2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.020408\n```\n:::\n:::\n\n\n\n\n- Very close to relative risk of 2.\n\n\n## More than 2 response categories\n\n\n* With 2 response categories, model the probability of one, and prob of other is one minus that. So doesn't matter which category you model.\n\n* With more than 2 categories, have to think more carefully about the categories: are they\n\n\n* *ordered*: you can put them in a natural order (like low, medium, high)\n\n* *nominal*: ordering the categories doesn't make sense (like red, green, blue).\n\n\n* R handles both kinds of response; learn how.\n\n\n\n## Ordinal response: the miners\n\n\n* \nModel probability of being in given category *or lower*.\n\n* Example: coal-miners often suffer disease pneumoconiosis. Likelihood of disease believed to be greater \namong miners who have worked longer. \n\n* Severity of disease measured on categorical scale: none,\nmoderate, severe.\n\n## Miners data\n\n* Data are frequencies:\n\n```\nExposure None Moderate Severe\n5.8 98 0 0\n15.0 51 2 1\n21.5 34 6 3\n27.5 35 5 8\n33.5 32 10 9\n39.5 23 7 8\n46.0 12 6 10\n51.5 4 2 5\n```\n\n\n\n\n## Reading the data\n\nData in aligned columns with more than one space between, so: \n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/miners-tab.txt\"\nfreqs <- read_table(my_url)\n```\n:::\n\n\n\\normalsize\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Exposure None Moderate Severe\n \n1 5.8 98 0 0\n2 15 51 2 1\n3 21.5 34 6 3\n4 27.5 35 5 8\n5 33.5 32 10 9\n6 39.5 23 7 8\n7 46 12 6 10\n8 51.5 4 2 5\n```\n:::\n:::\n\n\n\n \n\n\n## Tidying \n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>%\n pivot_longer(-Exposure, names_to = \"Severity\", values_to = \"Freq\") %>%\n mutate(Severity = fct_inorder(Severity)) -> miners\n```\n:::\n\n\n\n \n\n\n## Result\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 24 x 3\n Exposure Severity Freq\n \n 1 5.8 None 98\n 2 5.8 Moderate 0\n 3 5.8 Severe 0\n 4 15 None 51\n 5 15 Moderate 2\n 6 15 Severe 1\n 7 21.5 None 34\n 8 21.5 Moderate 6\n 9 21.5 Severe 3\n10 27.5 None 35\n# i 14 more rows\n```\n:::\n:::\n\n\n\\normalsize\n \n\n\n## Plot proportions against exposure\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nminers %>% \n group_by(Exposure) %>% \n mutate(proportion = Freq / sum(Freq)) -> prop\nggplot(prop, aes(x = Exposure, y = proportion,\n colour = Severity)) + \n geom_point() + geom_smooth(se = F)\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/bLogistic-28-1.pdf)\n:::\n:::\n\n\n\\normalsize\n\n\n## Reminder of data setup\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nminers\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 24 x 3\n Exposure Severity Freq\n \n 1 5.8 None 98\n 2 5.8 Moderate 0\n 3 5.8 Severe 0\n 4 15 None 51\n 5 15 Moderate 2\n 6 15 Severe 1\n 7 21.5 None 34\n 8 21.5 Moderate 6\n 9 21.5 Severe 3\n10 27.5 None 35\n# i 14 more rows\n```\n:::\n:::\n\n\n\n \n\\normalsize\n\n\n\n## Fitting ordered logistic model\n\nUse function `polr` from package `MASS`. Like `glm`.\n\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners\n)\n```\n:::\n\n\n \n\n\n## Output: not very illuminating\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.1 <- polr(Severity ~ Exposure,\n weights = Freq,\n data = miners,\n Hess = TRUE\n)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sev.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\npolr(formula = Severity ~ Exposure, data = miners, weights = Freq, \n Hess = TRUE)\n\nCoefficients:\n Value Std. Error t value\nExposure 0.0959 0.01194 8.034\n\nIntercepts:\n Value Std. Error t value\nNone|Moderate 3.9558 0.4097 9.6558\nModerate|Severe 4.8690 0.4411 11.0383\n\nResidual Deviance: 416.9188 \nAIC: 422.9188 \n```\n:::\n:::\n\n\n\\normalsize\n \n\n## Does exposure have an effect?\nFit model without `Exposure`, and compare\nusing `anova`. Note `1` for model with just intercept:\n\n\n\n::: {.cell}\n\n:::\n\n\n\n \n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsev.0 <- polr(Severity ~ 1, weights = Freq, data = miners)\nanova(sev.0, sev.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of ordinal regression models\n\nResponse: Severity\n Model Resid. df Resid. Dev Test Df LR stat.\n1 1 369 505.1621 \n2 Exposure 368 416.9188 1 vs 2 1 88.24324\n Pr(Chi)\n1 \n2 0\n```\n:::\n:::\n\n\n\\normalsize\n\nExposure definitely has effect on severity of disease. \n\n\n## Another way\n\n\n* What (if anything) can we drop from model with `exposure`?\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(sev.1, test = \"Chisq\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nSingle term deletions\n\nModel:\nSeverity ~ Exposure\n Df AIC LRT Pr(>Chi) \n 422.92 \nExposure 1 509.16 88.243 < 2.2e-16 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n \n\n\n* Nothing. Exposure definitely has effect.\n\n\n\n## Predicted probabilities 1/2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nfreqs %>% select(Exposure) -> new\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 1\n Exposure\n \n1 5.8\n2 15 \n3 21.5\n4 27.5\n5 33.5\n6 39.5\n7 46 \n8 51.5\n```\n:::\n:::\n\n\n\n\n## Predicted probabilities 2/2\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sev.1, newdata = new)) %>%\n select(group, estimate, Exposure) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Exposure None Moderate Severe\n \n1 5.8 0.968 0.0191 0.0132\n2 15 0.925 0.0433 0.0314\n3 21.5 0.869 0.0739 0.0569\n4 27.5 0.789 0.114 0.0969\n5 33.5 0.678 0.162 0.160 \n6 39.5 0.542 0.205 0.253 \n7 46 0.388 0.224 0.388 \n8 51.5 0.272 0.210 0.517 \n```\n:::\n:::\n\n\n\\normalsize\n\n## Plot of predicted probabilities\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(model = sev.1, condition = c(\"Exposure\", \"group\"),\n type = \"probs\") +\n geom_point(data = prop, aes(x = Exposure, y = proportion, \n colour = Severity)) -> ggg\n```\n:::\n\n\n\n## The graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggg\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-11-1.pdf)\n:::\n:::\n\n\n\n\n## Comments\n\n\n* Model appears to match data well enough.\n\n- As exposure goes up, prob of None\ngoes down, Severe goes up (sharply for high exposure).\n\n- So more exposure means worse disease.\n\n\n## Unordered responses\n\n\n* With unordered (nominal) responses, can use *generalized logit*.\n\n* Example: 735 people, record age and sex (male 0, female 1), which of 3 brands of some product preferred.\n\n* Data in `mlogit.csv` separated by commas (so\n`read_csv` will work):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/mlogit.csv\"\nbrandpref <- read_csv(my_url)\n```\n:::\n\n\n \n\n\n\n\n## The data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 735 x 3\n brand sex age\n \n 1 1 0 24\n 2 1 0 26\n 3 1 0 26\n 4 1 1 27\n 5 1 1 27\n 6 3 1 27\n 7 1 0 27\n 8 1 0 27\n 9 1 1 27\n10 1 0 27\n# i 725 more rows\n```\n:::\n:::\n\n\n\n \n\n\n## Bashing into shape\n\n\n* `sex` and `brand` not meaningful as numbers, so\nturn into factors:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n mutate(sex = ifelse(sex == 1, \"female\", \"male\"), \n sex = factor(sex),\n brand = factor(brand)\n ) -> brandpref\n```\n:::\n\n\n \n \n## Fitting model \n\n* We use `multinom` from package `nnet`. Works\nlike `polr`.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.1 <- multinom(brand ~ age + sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 12 (6 variable)\ninitial value 807.480032 \niter 10 value 702.990572\nfinal value 702.970704 \nconverged\n```\n:::\n:::\n\n\n \n\n\n## Can we drop anything?\n\n\n* Unfortunately `drop1` seems not to work:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(brands.1, test = \"Chisq\", trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \n```\n:::\n\n::: {.cell-output .cell-output-error}\n```\nError in if (trace) {: argument is not interpretable as logical\n```\n:::\n:::\n\n\n\n* So, fall back on fitting model without what you want to test, and\ncomparing using `anova`. \n\n\n\n## Do age/sex help predict brand? 1/3\n\nFit models without each of age and sex:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.2 <- multinom(brand ~ age, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \niter 10 value 706.796323\niter 10 value 706.796322\nfinal value 706.796322 \nconverged\n```\n:::\n\n```{.r .cell-code}\nbrands.3 <- multinom(brand ~ sex, data = brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 9 (4 variable)\ninitial value 807.480032 \nfinal value 791.861266 \nconverged\n```\n:::\n:::\n\n\n \n\n\n## Do age/sex help predict brand? 2/3\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(brands.2, brands.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df LR stat.\n1 age 1466 1413.593 \n2 age + sex 1464 1405.941 1 vs 2 2 7.651236\n Pr(Chi)\n1 \n2 0.02180496\n```\n:::\n\n```{.r .cell-code}\nanova(brands.3, brands.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df LR stat.\n1 sex 1466 1583.723 \n2 age + sex 1464 1405.941 1 vs 2 2 177.7811\n Pr(Chi)\n1 \n2 0\n```\n:::\n:::\n\n\n\\normalsize\n\n\n## Do age/sex help predict brand? 3/3\n\n\n* `age` definitely significant (second `anova`)\n\n* `sex` significant also (first `anova`), though P-value less dramatic\n\n* Keep both.\n- Expect to see a large effect of `age`, and a smaller one of `sex`.\n\n\n\n\n## Another way to build model\n\n\n* Start from model with everything and run `step`:\n\n\n\n::: {.cell}\n\n:::\n\n\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nstep(brands.1, trace = 0)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\ntrying - age \ntrying - sex \n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\nCall:\nmultinom(formula = brand ~ age + sex)\n\nCoefficients:\n (Intercept) age sexmale\n2 -11.25127 0.3682202 -0.5237736\n3 -22.25571 0.6859149 -0.4658215\n\nResidual Deviance: 1405.941 \nAIC: 1417.941 \n```\n:::\n:::\n\n\n\\normalsize\n \n\n* Final model contains both `age` and `sex` so neither\ncould be removed.\n\n\n## Making predictions\n\nFind age 5-number summary, and the two sexes:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(brandpref)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n brand sex age \n 1:207 female:466 Min. :24.0 \n 2:307 male :269 1st Qu.:32.0 \n 3:221 Median :32.0 \n Mean :32.9 \n 3rd Qu.:34.0 \n Max. :38.0 \n```\n:::\n:::\n\n\n\nSpace the ages out a bit for prediction (see over).\n\n\\normalsize\n\n## Combinations\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(age = c(24, 30, 33, 35, 38), \n sex = c(\"female\", \"male\"), model = brands.1)\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n brand age sex\n1 2 24 female\n2 2 24 male\n3 2 30 female\n4 2 30 male\n5 2 33 female\n6 2 33 male\n7 2 35 female\n8 2 35 male\n9 2 38 female\n10 2 38 male\n```\n:::\n:::\n\n\n\n## The predictions\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(brands.1, newdata = new)) %>%\n select(group, estimate, age, sex) %>% \n pivot_wider(names_from = group, values_from = estimate)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 5\n age sex `1` `2` `3`\n \n 1 24 female 0.915 0.0819 0.00279\n 2 24 male 0.948 0.0502 0.00181\n 3 30 female 0.500 0.407 0.0933 \n 4 30 male 0.625 0.302 0.0732 \n 5 33 female 0.203 0.500 0.297 \n 6 33 male 0.296 0.432 0.272 \n 7 35 female 0.0840 0.432 0.484 \n 8 35 male 0.131 0.397 0.472 \n 9 38 female 0.0162 0.252 0.732 \n10 38 male 0.0260 0.239 0.735 \n```\n:::\n:::\n\n\n\n## Comments\n\n* Young males prefer brand 1, \nbut older males prefer brand 3.\n\n* Females similar, but like brand 1 less and\nbrand 2 more.\n\n- A clear `brand` effect, but the `sex` effect is less clear. \n\n## Making a plot\n\n- `plot_predictions` doesn't quite work\n- so don't draw, edit, *then* make graph:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(brands.1, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g\n```\n:::\n\n\n\n## The graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-16-1.pdf)\n:::\n:::\n\n\n \n\n## Digesting the plot\n\n\n* Brand vs.\\ age: younger people (of both genders) prefer brand\n1, but older people (of both genders) prefer brand 3. (Explains\nsignificant age effect.)\n\n* Brand vs.\\ sex: females (solid) like brand 1 less than males\n(dashed), like brand 2 more (for all ages). \n\n* Not much brand difference between genders (solid and dashed\nlines of same colours close), but enough to be significant.\n\n* Model didn't include interaction, so modelled effect of gender\non brand same for each age, modelled effect of age same for each\ngender. (See also later.) \n\n\n## Alternative data format\n\nSummarize all people of same brand preference, same sex, same age on one line of data file with frequency on end:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 735 x 3\n brand sex age\n \n 1 1 male 24\n 2 1 male 26\n 3 1 male 26\n 4 1 female 27\n 5 1 female 27\n 6 3 female 27\n 7 1 male 27\n 8 1 male 27\n 9 1 female 27\n10 1 male 27\n# i 725 more rows\n```\n:::\n:::\n\n\n\n\n```\n1 0 24 1\n1 0 26 2\n1 0 27 4\n1 0 28 4\n1 0 29 7\n1 0 30 3\n...\n```\n\nWhole data set in 65 lines not 735! But how?\n\n\n## Getting alternative data format\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrandpref %>%\n group_by(age, sex, brand) %>%\n summarize(Freq = n()) %>%\n ungroup() -> b\nb\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 65 x 4\n age sex brand Freq\n \n 1 24 male 1 1\n 2 26 male 1 2\n 3 27 female 1 4\n 4 27 female 3 1\n 5 27 male 1 4\n 6 28 female 1 6\n 7 28 female 2 2\n 8 28 female 3 1\n 9 28 male 1 4\n10 28 male 3 2\n# i 55 more rows\n```\n:::\n:::\n\n\n\n \n\n## Fitting models, almost the same\n\n\n* Just have to remember `weights` to incorporate\nfrequencies.\n\n* Otherwise `multinom` assumes you have just 1 obs\non each line!\n\n* Again turn (numerical) `sex` and `brand` into factors:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nb %>%\n mutate(sex = factor(sex)) %>%\n mutate(brand = factor(brand)) -> bf\nb.1 <- multinom(brand ~ age + sex, data = bf, weights = Freq)\nb.2 <- multinom(brand ~ age, data = bf, weights = Freq)\n```\n:::\n\n\n\\normalsize\n\n\n\n## P-value for `sex` identical\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(b.2, b.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df LR stat.\n1 age 126 1413.593 \n2 age + sex 124 1405.941 1 vs 2 2 7.651236\n Pr(Chi)\n1 \n2 0.02180496\n```\n:::\n:::\n\n\n\\normalsize\n\nSame P-value as before, so we haven't changed anything important.\n\n\n\n\n## Trying interaction between age and gender\n\n\n\n::: {.cell}\n\n:::\n\n\n\n \n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nbrands.4 <- update(brands.1, . ~ . + age:sex)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# weights: 15 (8 variable)\ninitial value 807.480032 \niter 10 value 703.191146\niter 20 value 702.572260\niter 30 value 702.570900\niter 30 value 702.570893\niter 30 value 702.570893\nfinal value 702.570893 \nconverged\n```\n:::\n\n```{.r .cell-code}\nanova(brands.1, brands.4)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nLikelihood ratio tests of Multinomial Models\n\nResponse: brand\n Model Resid. df Resid. Dev Test Df\n1 age + sex 1464 1405.941 \n2 age + sex + age:sex 1462 1405.142 1 vs 2 2\n LR stat. Pr(Chi)\n1 \n2 0.7996223 0.6704466\n```\n:::\n:::\n\n\n\n \n\\normalsize\n\n* No evidence that effect of age on brand preference differs for\nthe two genders.\n\n## Make graph again\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplot_predictions(brands.4, condition = c(\"age\", \"brand\", \"sex\"), \n type = \"probs\", draw = FALSE) %>% \n ggplot(aes(x = age, y = estimate, colour = group, \n linetype = sex)) +\n geom_line() -> g4\n```\n:::\n\n\n\n\n## Not much difference in the graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng4\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-19-1.pdf)\n:::\n:::\n\n\n\n## Compare model without interaction\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](logistic_files/figure-beamer/unnamed-chunk-20-1.pdf)\n:::\n:::\n", "supporting": [ "logistic_files/figure-beamer" ], diff --git a/_freeze/logistic/figure-beamer/bLogistic-28-1.pdf b/_freeze/logistic/figure-beamer/bLogistic-28-1.pdf index e6a4230..be57f18 100644 Binary files a/_freeze/logistic/figure-beamer/bLogistic-28-1.pdf and b/_freeze/logistic/figure-beamer/bLogistic-28-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/unnamed-chunk-1-1.pdf b/_freeze/logistic/figure-beamer/unnamed-chunk-1-1.pdf index a735a22..a05d698 100644 Binary files a/_freeze/logistic/figure-beamer/unnamed-chunk-1-1.pdf and b/_freeze/logistic/figure-beamer/unnamed-chunk-1-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/unnamed-chunk-11-1.pdf b/_freeze/logistic/figure-beamer/unnamed-chunk-11-1.pdf index 6cbc7f0..7449b4b 100644 Binary files a/_freeze/logistic/figure-beamer/unnamed-chunk-11-1.pdf and b/_freeze/logistic/figure-beamer/unnamed-chunk-11-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/unnamed-chunk-16-1.pdf b/_freeze/logistic/figure-beamer/unnamed-chunk-16-1.pdf index 06a1348..d257a8b 100644 Binary files a/_freeze/logistic/figure-beamer/unnamed-chunk-16-1.pdf and b/_freeze/logistic/figure-beamer/unnamed-chunk-16-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/unnamed-chunk-19-1.pdf b/_freeze/logistic/figure-beamer/unnamed-chunk-19-1.pdf index 4363cef..b89d703 100644 Binary files a/_freeze/logistic/figure-beamer/unnamed-chunk-19-1.pdf and b/_freeze/logistic/figure-beamer/unnamed-chunk-19-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/unnamed-chunk-2-1.pdf b/_freeze/logistic/figure-beamer/unnamed-chunk-2-1.pdf index fc06564..4fb23fa 100644 Binary files a/_freeze/logistic/figure-beamer/unnamed-chunk-2-1.pdf and b/_freeze/logistic/figure-beamer/unnamed-chunk-2-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/unnamed-chunk-20-1.pdf b/_freeze/logistic/figure-beamer/unnamed-chunk-20-1.pdf index c8ac691..a9298e7 100644 Binary files a/_freeze/logistic/figure-beamer/unnamed-chunk-20-1.pdf and b/_freeze/logistic/figure-beamer/unnamed-chunk-20-1.pdf differ diff --git a/_freeze/logistic/figure-beamer/virtusentella-1.pdf b/_freeze/logistic/figure-beamer/virtusentella-1.pdf index 193e4cf..2dc080c 100644 Binary files a/_freeze/logistic/figure-beamer/virtusentella-1.pdf and b/_freeze/logistic/figure-beamer/virtusentella-1.pdf differ diff --git a/_freeze/logistic/figure-revealjs/unnamed-chunk-1-1.png b/_freeze/logistic/figure-revealjs/unnamed-chunk-1-1.png index 56bec2b..59a1c0b 100644 Binary files a/_freeze/logistic/figure-revealjs/unnamed-chunk-1-1.png and b/_freeze/logistic/figure-revealjs/unnamed-chunk-1-1.png differ diff --git a/_freeze/logistic/figure-revealjs/unnamed-chunk-11-1.png b/_freeze/logistic/figure-revealjs/unnamed-chunk-11-1.png index 97064cc..f8b3bb8 100644 Binary files a/_freeze/logistic/figure-revealjs/unnamed-chunk-11-1.png and b/_freeze/logistic/figure-revealjs/unnamed-chunk-11-1.png differ diff --git a/_freeze/readfile/execute-results/html.json b/_freeze/readfile/execute-results/html.json index 5927781..e6fdf93 100644 --- a/_freeze/readfile/execute-results/html.json +++ b/_freeze/readfile/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "22ac5c5dbfc91b88360ac2c49fd8e4d5", + "hash": "72af293945eb209714339eeb05e73795", "result": { - "markdown": "---\ntitle: \"Reading data files\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Introduction\n\n- First thing we need to do is to read in data, so that we can use our\n software to analyze.\n- Consider these:\n - Spreadsheet data saved as `.csv` file.\n - \"Delimited\" data such as values separated by spaces.\n - Actual Excel spreadsheets.\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## A spreadsheet\n\n![](spreadsheet.png)\n\n## Save as `.csv`\n\n- `.csv` or \"comma-separated values\" is a way of turning spreadsheet\n values into plain text.\n- Easy to read into R\n- but does not preserve formulas. (This is a reason for doing all your\n calculations in your statistical software, and only having data in\n your spreadsheet.)\n- File, Save As Text CSV (or similar).\n- used name `test1.csv`.\n\n## The `.csv` file\n\n``` \nid,x,y,group\np1,10,21,upper\np2,11,20,lower\np3,13,25,upper\np4,15,27,lower\np5,16,30,upper\np6,17,31,lower\n```\n\nTo read this in:\n\n- Fire up R Studio at `r.datatools.utoronto.ca`\n- Upload this .csv file. (Bottom right, next to New Folder, Upload.)\n Click Choose File, find the file, click Open. Click OK. See the file\n appear bottom right.\n\n## Make a new Quarto document\n\n- File, New File, Quarto Document\n- ...and get rid of the template document (leaving the first four\n lines).\n- Make a code chunk and in it put this. Run it.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## Reading in the file\n\n- Use `read_csv` with the name of the file, in quotes. Save the\n read-in file in something, here called `mydata`. Make a new code\n chunk for this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(\"test1.csv\")\nmydata\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## More on the above\n\n- read_csv guesses what kind of thing is in each column. Here it\n correctly guesses that:\n - id and group are text (categorical variables). id is actually\n \"identifier variable\": identifies individuals.\n - x and y are \"double\": numbers that might have a decimal point in\n them.\n\n## R Studio on your own computer\n\n- Put the .csv file in the same folder as your project. Then read it\n in as above like `read_csv(\"test1.csv\")`.\n- Or, use\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# f <- file.choose()\nf\n```\n:::\n\n\nwhich brings up a file selector (as if you were going to find a file to\nload or save it). Find your `.csv` file, the address of which will be\nsaved in `f`, and then:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(f)\n```\n:::\n\n\n- When you have selected the file, comment out the `file.choose` line\n by putting a \\# on the front of it. That will save you having to\n find the file again by mistake. (Keyboard shortcut: go to the line,\n type control-shift-C or Mac equivalent with Cmd.)\n\n## Looking at what we read in\n\n- Again, type the name of the thing to display it:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n- This is a \"tibble\" or data frame, the standard way of storing a data\n set in R.\n- Tibbles print as much as will display on the screen. If there are\n more rows or columns, it will say so.\n- You will see navigation keys to display more rows or columns (if\n there are more).\n\n## `View`-ing your data frame\n\n- Another way to examine your data frame is to View it, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nView(mydata)\n```\n:::\n\n\n...or find your data frame in the Global Environment top right and click\nit. - This pops up a \"data frame viewer\" top left:\n\n![](viewview.png){height=\"500px\"}\n\n## This View\n\n- Read-only: cannot edit data\n- Can display data satisfying conditions: click on Filter, then:\n - for a categorical variable, type name of category you want\n - for a quantitative variable, use slider to describe values you\n want.\n- Can sort a column into ascending or descending order (click little\n arrows next to column name).\n- Clicking the symbol with arrow on it left of Filter \"pops out\" View\n into separate (bigger) window.\n\n## Summarizing what we read in\n\n- It is always a good idea to look at your data after you have read it\n in, to make sure you have believable numbers (and the right number\n of individuals and variables).\n- Quick check for errors: these often show up as values too high or\n too low, so the min and/or max will be unreasonable.\n- Five-number summary:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(mydata)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n id x y group \n Length:6 Min. :10.00 Min. :20.00 Length:6 \n Class :character 1st Qu.:11.50 1st Qu.:22.00 Class :character \n Mode :character Median :14.00 Median :26.00 Mode :character \n Mean :13.67 Mean :25.67 \n 3rd Qu.:15.75 3rd Qu.:29.25 \n Max. :17.00 Max. :31.00 \n```\n:::\n:::\n\n\n- Quantitative, five-number summary plus mean.\n- Categorical, how many rows.\n\n## Reading from a URL\n\n- Any data file on the Web can be read directly.\n- [Example data link:](http://ritsokiguess.site/datafiles/global.csv)\n- Use URL instead of filename.\n- I like to save the URL in a variable first (because URLs tend to be\n long), and then put that variable in the `read_` function:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/global.csv\"\nmy_url\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"http://ritsokiguess.site/datafiles/global.csv\"\n```\n:::\n\n```{.r .cell-code}\nglobal <- read_csv(my_url)\n```\n:::\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglobal\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Space-delimited files\n\n- Another common format for data is a text file with the values\n separated by spaces. Top of some other data:\n\n``` \ncup tempdiff \nStarbucks 13 \nStarbucks 7 \nStarbucks 7 \nStarbucks 17.5 \nStarbucks 10 \nStarbucks 15.5 \nStarbucks 6 \nStarbucks 6 \nSIGG 12 \nSIGG 16 \nSIGG 9 \nSIGG 23 \nSIGG 11 \nSIGG 20.5 \nSIGG 12.5 \nSIGG 20.5 \nSIGG 24.5 \nCUPPS 6 \nCUPPS 6 \nCUPPS 18.5 \nCUPPS 10 \n```\n\n## Reading the coffee data\n\n- This file was on my computer so I uploaded it to\n `r.datatools.utoronto.ca` first.\n- This time, `read_delim`, and we also have to say what the thing is\n separating the values:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee <- read_delim(\"coffee.txt\", \" \")\ncoffee\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Name of the cup, text, and tempdiff, a decimal number.\n\n## Looking at the values (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThese were four brands of travel mug (in cup), and for each, how much\nthe temperature of the coffee in the mug decreased over 30 minutes.\n\n## Reading from the Web; the soap data\n\n- Use the URL in place of the filename.\n- Save the URL in a variable first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- url(\"http://ritsokiguess.site/datafiles/soap.txt\")\nsoap <- read_delim(url, \" \")\n```\n:::\n\n\n## The soap data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsoap\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Data aligned in columns\n\n- Sometimes you see data aligned in columns, thus:\n\n![](Screenshot_2019-04-24_20-16-24.png){height=\"400px\"}\n\n- `read_delim` will not work: values separated by more than one space.\n- The number of spaces between values is not constant, because there\n is one fewer space before the 10.\n- `read_table` works for this.\n\n## Reading in column-aligned data\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrugs <- read_table(\"migraine.txt\")\ndrugs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Reading an Excel sheet directly\n\n- Here is [my spreadsheet](test2.xlsx) from before, but tarted up a\n bit:\n\n![](excel.png){height=\"450px\"}\n\n- It is now a workbook with a second sheet called \"notes\" (that we\n don't want).\n- Install package `readxl` first.\n\n## Reading it in\n\n- Read into R, saying that we only want the sheet \"data\". Upload\n spreadsheet first.\n- Excel spreadsheets must be \"local\": cannot read one in from a URL.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(readxl)\nmydata2 <- read_excel(\"test2.xlsx\", sheet = \"data\")\nmydata2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n", + "markdown": "---\ntitle: \"Reading data files\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Introduction\n\n- First thing we need to do is to read in data, so that we can use our\n software to analyze.\n- Consider these:\n - Spreadsheet data saved as `.csv` file.\n - \"Delimited\" data such as values separated by spaces.\n - Actual Excel spreadsheets.\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## A spreadsheet\n\n![](spreadsheet.png)\n\n## Save as `.csv`\n\n- `.csv` or \"comma-separated values\" is a way of turning spreadsheet\n values into plain text.\n- Easy to read into R\n- but does not preserve formulas. (This is a reason for doing all your\n calculations in your statistical software, and only having data in\n your spreadsheet.)\n- File, Save As Text CSV (or similar).\n- used name `test1.csv`.\n\n## The `.csv` file\n\n``` \nid,x,y,group\np1,10,21,upper\np2,11,20,lower\np3,13,25,upper\np4,15,27,lower\np5,16,30,upper\np6,17,31,lower\n```\n\nTo read this in:\n\n- Fire up R Studio at `r.datatools.utoronto.ca`\n- Upload this .csv file. (Bottom right, next to New Folder, Upload.)\n Click Choose File, find the file, click Open. Click OK. See the file\n appear bottom right.\n\n## Make a new Quarto document\n\n- File, New File, Quarto Document\n- ...and get rid of the template document (leaving the first four\n lines).\n- Make a code chunk and in it put this. Run it.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## Reading in the file\n\n- Use `read_csv` with the name of the file, in quotes. Save the\n read-in file in something, here called `mydata`. Make a new code\n chunk for this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(\"test1.csv\")\nmydata\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## More on the above\n\n- read_csv guesses what kind of thing is in each column. Here it\n correctly guesses that:\n - id and group are text (categorical variables). id is actually\n \"identifier variable\": identifies individuals.\n - x and y are \"double\": numbers that might have a decimal point in\n them.\n\n## R Studio on your own computer\n\n- Put the .csv file in the same folder as your project. Then read it\n in as above like `read_csv(\"test1.csv\")`.\n- Or, use\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# f <- file.choose()\nf\n```\n:::\n\n\nwhich brings up a file selector (as if you were going to find a file to\nload or save it). Find your `.csv` file, the address of which will be\nsaved in `f`, and then:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(f)\n```\n:::\n\n\n- When you have selected the file, comment out the `file.choose` line\n by putting a \\# on the front of it. That will save you having to\n find the file again by mistake. (Keyboard shortcut: go to the line,\n type control-shift-C or Mac equivalent with Cmd.)\n\n## Looking at what we read in\n\n- Again, type the name of the thing to display it:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n- This is a \"tibble\" or data frame, the standard way of storing a data\n set in R.\n- Tibbles print as much as will display on the screen. If there are\n more rows or columns, it will say so.\n- You will see navigation keys to display more rows or columns (if\n there are more).\n\n## `View`-ing your data frame\n\n- Another way to examine your data frame is to View it, like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nView(mydata)\n```\n:::\n\n\n...or find your data frame in the Global Environment top right and click\nit. - This pops up a \"data frame viewer\" top left:\n\n![](viewview.png){height=\"500px\"}\n\n## This View\n\n- Read-only: cannot edit data\n- Can display data satisfying conditions: click on Filter, then:\n - for a categorical variable, type name of category you want\n - for a quantitative variable, use slider to describe values you\n want.\n- Can sort a column into ascending or descending order (click little\n arrows next to column name).\n- Clicking the symbol with arrow on it left of Filter \"pops out\" View\n into separate (bigger) window.\n\n## Summarizing what we read in\n\n- It is always a good idea to look at your data after you have read it\n in, to make sure you have believable numbers (and the right number\n of individuals and variables).\n- Quick check for errors: these often show up as values too high or\n too low, so the min and/or max will be unreasonable.\n- Five-number summary:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(mydata)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n id x y group \n Length:6 Min. :10.00 Min. :20.00 Length:6 \n Class :character 1st Qu.:11.50 1st Qu.:22.00 Class :character \n Mode :character Median :14.00 Median :26.00 Mode :character \n Mean :13.67 Mean :25.67 \n 3rd Qu.:15.75 3rd Qu.:29.25 \n Max. :17.00 Max. :31.00 \n```\n:::\n:::\n\n\n- Quantitative, five-number summary plus mean.\n- Categorical, how many rows.\n\n## Reading from a URL\n\n- Any data file on the Web can be read directly.\n- [Example data link:](http://ritsokiguess.site/datafiles/global.csv)\n- Use URL instead of filename.\n- I like to save the URL in a variable first (because URLs tend to be\n long), and then put that variable in the `read_` function:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/global.csv\"\nmy_url\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"http://ritsokiguess.site/datafiles/global.csv\"\n```\n:::\n\n```{.r .cell-code}\nglobal <- read_csv(my_url)\n```\n:::\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglobal\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Space-delimited files\n\n- Another common format for data is a text file with the values\n separated by spaces. Top of some other data:\n\n``` \ncup tempdiff \nStarbucks 13 \nStarbucks 7 \nStarbucks 7 \nStarbucks 17.5 \nStarbucks 10 \nStarbucks 15.5 \nStarbucks 6 \nStarbucks 6 \nSIGG 12 \nSIGG 16 \nSIGG 9 \nSIGG 23 \nSIGG 11 \nSIGG 20.5 \nSIGG 12.5 \nSIGG 20.5 \nSIGG 24.5 \nCUPPS 6 \nCUPPS 6 \nCUPPS 18.5 \nCUPPS 10 \n```\n\n## Reading the coffee data\n\n- This file was on my computer so I uploaded it to\n `r.datatools.utoronto.ca` first.\n- This time, `read_delim`, and we also have to say what the thing is\n separating the values:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee <- read_delim(\"coffee.txt\", \" \")\ncoffee\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Name of the cup, text, and tempdiff, a decimal number.\n\n## Looking at the values (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThese were four brands of travel mug (in cup), and for each, how much\nthe temperature of the coffee in the mug decreased over 30 minutes.\n\n## Reading from the Web; the soap data\n\n- Use the URL in place of the filename.\n- Save the URL in a variable first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- \"http://ritsokiguess.site/datafiles/soap.txt\"\nsoap <- read_delim(url, \" \")\n```\n:::\n\n\n## The soap data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsoap\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Data aligned in columns\n\n- Sometimes you see data aligned in columns, thus:\n\n![](Screenshot_2019-04-24_20-16-24.png){height=\"400px\"}\n\n- `read_delim` will not work: values separated by more than one space.\n- The number of spaces between values is not constant, because there\n is one fewer space before the 10.\n- `read_table` works for this.\n\n## Reading in column-aligned data\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrugs <- read_table(\"migraine.txt\")\ndrugs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Reading an Excel sheet directly\n\n- Here is [my spreadsheet](test2.xlsx) from before, but tarted up a\n bit:\n\n![](excel.png){height=\"450px\"}\n\n- It is now a workbook with a second sheet called \"notes\" (that we\n don't want).\n- Install package `readxl` first.\n\n\n::: {.cell}\n\n:::\n\n\n## Reading it in\n\n- Read into R, saying that we only want the sheet \"data\". Upload\n spreadsheet first.\n- Excel spreadsheets must be \"local\": cannot read one in from a URL.\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# install.packages(\"readxl\")\nlibrary(readxl)\nmydata2 <- read_excel(\"test2.xlsx\", sheet = \"data\")\nmydata2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n", "supporting": [ "readfile_files" ], diff --git a/_freeze/readfile/execute-results/tex.json b/_freeze/readfile/execute-results/tex.json index 3d20c7e..c73b3a0 100644 --- a/_freeze/readfile/execute-results/tex.json +++ b/_freeze/readfile/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "22ac5c5dbfc91b88360ac2c49fd8e4d5", + "hash": "72af293945eb209714339eeb05e73795", "result": { - "markdown": "---\ntitle: \"Reading data files\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Introduction\n\n- First thing we need to do is to read in data, so that we can use our\n software to analyze.\n- Consider these:\n - Spreadsheet data saved as `.csv` file.\n - \"Delimited\" data such as values separated by spaces.\n - Actual Excel spreadsheets.\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## A spreadsheet\n\n![](spreadsheet.png)\n\n## Save as `.csv`\n\n- `.csv` or \"comma-separated values\" is a way of turning spreadsheet\n values into plain text.\n- Easy to read into R\n- but does not preserve formulas. (This is a reason for doing all your\n calculations in your statistical software, and only having data in\n your spreadsheet.)\n- File, Save As Text CSV (or similar).\n- used name `test1.csv`.\n\n## The `.csv` file\n\n``` \nid,x,y,group\np1,10,21,upper\np2,11,20,lower\np3,13,25,upper\np4,15,27,lower\np5,16,30,upper\np6,17,31,lower\n```\n\nTo read this in:\n\n- Fire up R Studio at `r.datatools.utoronto.ca`\n- Upload this .csv file. (Bottom right, next to New Folder, Upload.)\n Click Choose File, find the file, click Open. Click OK. See the file\n appear bottom right.\n\n## Make a new Quarto document\n\n- File, New File, Quarto Document\n- ...and get rid of the template document (leaving the first four\n lines).\n- Make a code chunk and in it put this. Run it.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Reading in the file\n\n- Use `read_csv` with the name of the file, in quotes. Save the\n read-in file in something, here called `mydata`. Make a new code\n chunk for this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(\"test1.csv\")\nmydata\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 4\n id x y group\n \n1 p1 10 21 upper\n2 p2 11 20 lower\n3 p3 13 25 upper\n4 p4 15 27 lower\n5 p5 16 30 upper\n6 p6 17 31 lower\n```\n:::\n:::\n\n\n\n## More on the above\n\n- read_csv guesses what kind of thing is in each column. Here it\n correctly guesses that:\n - id and group are text (categorical variables). id is actually\n \"identifier variable\": identifies individuals.\n - x and y are \"double\": numbers that might have a decimal point in\n them.\n\n## R Studio on your own computer\n\n- Put the .csv file in the same folder as your project. Then read it\n in as above like `read_csv(\"test1.csv\")`.\n- Or, use\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# f <- file.choose()\nf\n```\n:::\n\n\n\nwhich brings up a file selector (as if you were going to find a file to\nload or save it). Find your `.csv` file, the address of which will be\nsaved in `f`, and then:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(f)\n```\n:::\n\n\n\n- When you have selected the file, comment out the `file.choose` line\n by putting a \\# on the front of it. That will save you having to\n find the file again by mistake. (Keyboard shortcut: go to the line,\n type control-shift-C or Mac equivalent with Cmd.)\n\n## Looking at what we read in\n\n- Again, type the name of the thing to display it:\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 4\n id x y group\n \n1 p1 10 21 upper\n2 p2 11 20 lower\n3 p3 13 25 upper\n4 p4 15 27 lower\n5 p5 16 30 upper\n6 p6 17 31 lower\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- This is a \"tibble\" or data frame, the standard way of storing a data\n set in R.\n- Tibbles print as much as will display on the screen. If there are\n more rows or columns, it will say so.\n- You will see navigation keys to display more rows or columns (if\n there are more).\n\n## `View`-ing your data frame\n\n- Another way to examine your data frame is to View it, like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nView(mydata)\n```\n:::\n\n\n\n...or find your data frame in the Global Environment top right and click\nit. - This pops up a \"data frame viewer\" top left:\n\n![](viewview.png){height=\"500px\"}\n\n## This View\n\n- Read-only: cannot edit data\n- Can display data satisfying conditions: click on Filter, then:\n - for a categorical variable, type name of category you want\n - for a quantitative variable, use slider to describe values you\n want.\n- Can sort a column into ascending or descending order (click little\n arrows next to column name).\n- Clicking the symbol with arrow on it left of Filter \"pops out\" View\n into separate (bigger) window.\n\n## Summarizing what we read in\n\n- It is always a good idea to look at your data after you have read it\n in, to make sure you have believable numbers (and the right number\n of individuals and variables).\n- Quick check for errors: these often show up as values too high or\n too low, so the min and/or max will be unreasonable.\n- Five-number summary:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(mydata)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n id x y group \n Length:6 Min. :10.00 Min. :20.00 Length:6 \n Class :character 1st Qu.:11.50 1st Qu.:22.00 Class :character \n Mode :character Median :14.00 Median :26.00 Mode :character \n Mean :13.67 Mean :25.67 \n 3rd Qu.:15.75 3rd Qu.:29.25 \n Max. :17.00 Max. :31.00 \n```\n:::\n:::\n\n\n\n- Quantitative, five-number summary plus mean.\n- Categorical, how many rows.\n\n## Reading from a URL\n\n- Any data file on the Web can be read directly.\n- [Example data link:](http://ritsokiguess.site/datafiles/global.csv)\n- Use URL instead of filename.\n- I like to save the URL in a variable first (because URLs tend to be\n long), and then put that variable in the `read_` function:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/global.csv\"\nmy_url\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"http://ritsokiguess.site/datafiles/global.csv\"\n```\n:::\n\n```{.r .cell-code}\nglobal <- read_csv(my_url)\n```\n:::\n\n\n\n## The data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglobal\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 3\n warehouse size cost\n \n 1 A 225 12.0 \n 2 B 350 14.1 \n 3 A 150 8.93\n 4 A 200 11.0 \n 5 A 175 10.0 \n 6 A 180 10.1 \n 7 B 325 13.8 \n 8 B 290 13.3 \n 9 B 400 15 \n10 A 125 7.97\n```\n:::\n:::\n\n\n\n## Space-delimited files\n\n- Another common format for data is a text file with the values\n separated by spaces. Top of some other data:\n\n``` \ncup tempdiff \nStarbucks 13 \nStarbucks 7 \nStarbucks 7 \nStarbucks 17.5 \nStarbucks 10 \nStarbucks 15.5 \nStarbucks 6 \nStarbucks 6 \nSIGG 12 \nSIGG 16 \nSIGG 9 \nSIGG 23 \nSIGG 11 \nSIGG 20.5 \nSIGG 12.5 \nSIGG 20.5 \nSIGG 24.5 \nCUPPS 6 \nCUPPS 6 \nCUPPS 18.5 \nCUPPS 10 \n```\n\n## Reading the coffee data\n\n- This file was on my computer so I uploaded it to\n `r.datatools.utoronto.ca` first.\n- This time, `read_delim`, and we also have to say what the thing is\n separating the values:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee <- read_delim(\"coffee.txt\", \" \")\ncoffee\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 32 x 2\n cup tempdiff\n \n 1 Starbucks 13 \n 2 Starbucks 7 \n 3 Starbucks 7 \n 4 Starbucks 17.5\n 5 Starbucks 10 \n 6 Starbucks 15.5\n 7 Starbucks 6 \n 8 Starbucks 6 \n 9 SIGG 12 \n10 SIGG 16 \n# i 22 more rows\n```\n:::\n:::\n\n\n\n- Name of the cup, text, and tempdiff, a decimal number.\n\n## Looking at the values (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 32 x 2\n cup tempdiff\n \n 1 Starbucks 13 \n 2 Starbucks 7 \n 3 Starbucks 7 \n 4 Starbucks 17.5\n 5 Starbucks 10 \n 6 Starbucks 15.5\n 7 Starbucks 6 \n 8 Starbucks 6 \n 9 SIGG 12 \n10 SIGG 16 \n# i 22 more rows\n```\n:::\n:::\n\n\n\nThese were four brands of travel mug (in cup), and for each, how much\nthe temperature of the coffee in the mug decreased over 30 minutes.\n\n## Reading from the Web; the soap data\n\n- Use the URL in place of the filename.\n- Save the URL in a variable first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- url(\"http://ritsokiguess.site/datafiles/soap.txt\")\nsoap <- read_delim(url, \" \")\n```\n:::\n\n\n\n## The soap data (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsoap\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 27 x 4\n case scrap speed line \n \n 1 1 218 100 a \n 2 2 248 125 a \n 3 3 360 220 a \n 4 4 351 205 a \n 5 5 470 300 a \n 6 6 394 255 a \n 7 7 332 225 a \n 8 8 321 175 a \n 9 9 410 270 a \n10 10 260 170 a \n# i 17 more rows\n```\n:::\n:::\n\n\n\n## Data aligned in columns\n\n- Sometimes you see data aligned in columns, thus:\n\n![](Screenshot_2019-04-24_20-16-24.png){height=\"400px\"}\n\n- `read_delim` will not work: values separated by more than one space.\n- The number of spaces between values is not constant, because there\n is one fewer space before the 10.\n- `read_table` works for this.\n\n## Reading in column-aligned data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrugs <- read_table(\"migraine.txt\")\ndrugs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 9 x 3\n DrugA DrugB DrugC\n \n1 4 6 6\n2 5 8 7\n3 4 4 6\n4 3 5 6\n5 2 4 7\n6 4 6 5\n7 3 5 6\n8 4 10 5\n9 4 6 5\n```\n:::\n:::\n\n\n\n## Reading an Excel sheet directly\n\n- Here is [my spreadsheet](test2.xlsx) from before, but tarted up a\n bit:\n\n![](excel.png){height=\"450px\"}\n\n- It is now a workbook with a second sheet called \"notes\" (that we\n don't want).\n- Install package `readxl` first.\n\n## Reading it in\n\n- Read into R, saying that we only want the sheet \"data\". Upload\n spreadsheet first.\n- Excel spreadsheets must be \"local\": cannot read one in from a URL.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(readxl)\nmydata2 <- read_excel(\"test2.xlsx\", sheet = \"data\")\nmydata2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 4\n id x y group\n \n1 p1 10 21 upper\n2 p2 11 20 lower\n3 p3 13 25 upper\n4 p4 15 27 lower\n5 p5 16 30 upper\n6 p6 17 31 lower\n```\n:::\n:::\n", + "markdown": "---\ntitle: \"Reading data files\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Introduction\n\n- First thing we need to do is to read in data, so that we can use our\n software to analyze.\n- Consider these:\n - Spreadsheet data saved as `.csv` file.\n - \"Delimited\" data such as values separated by spaces.\n - Actual Excel spreadsheets.\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## A spreadsheet\n\n![](spreadsheet.png)\n\n## Save as `.csv`\n\n- `.csv` or \"comma-separated values\" is a way of turning spreadsheet\n values into plain text.\n- Easy to read into R\n- but does not preserve formulas. (This is a reason for doing all your\n calculations in your statistical software, and only having data in\n your spreadsheet.)\n- File, Save As Text CSV (or similar).\n- used name `test1.csv`.\n\n## The `.csv` file\n\n``` \nid,x,y,group\np1,10,21,upper\np2,11,20,lower\np3,13,25,upper\np4,15,27,lower\np5,16,30,upper\np6,17,31,lower\n```\n\nTo read this in:\n\n- Fire up R Studio at `r.datatools.utoronto.ca`\n- Upload this .csv file. (Bottom right, next to New Folder, Upload.)\n Click Choose File, find the file, click Open. Click OK. See the file\n appear bottom right.\n\n## Make a new Quarto document\n\n- File, New File, Quarto Document\n- ...and get rid of the template document (leaving the first four\n lines).\n- Make a code chunk and in it put this. Run it.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Reading in the file\n\n- Use `read_csv` with the name of the file, in quotes. Save the\n read-in file in something, here called `mydata`. Make a new code\n chunk for this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(\"test1.csv\")\nmydata\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 4\n id x y group\n \n1 p1 10 21 upper\n2 p2 11 20 lower\n3 p3 13 25 upper\n4 p4 15 27 lower\n5 p5 16 30 upper\n6 p6 17 31 lower\n```\n:::\n:::\n\n\n\n## More on the above\n\n- read_csv guesses what kind of thing is in each column. Here it\n correctly guesses that:\n - id and group are text (categorical variables). id is actually\n \"identifier variable\": identifies individuals.\n - x and y are \"double\": numbers that might have a decimal point in\n them.\n\n## R Studio on your own computer\n\n- Put the .csv file in the same folder as your project. Then read it\n in as above like `read_csv(\"test1.csv\")`.\n- Or, use\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# f <- file.choose()\nf\n```\n:::\n\n\n\nwhich brings up a file selector (as if you were going to find a file to\nload or save it). Find your `.csv` file, the address of which will be\nsaved in `f`, and then:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata <- read_csv(f)\n```\n:::\n\n\n\n- When you have selected the file, comment out the `file.choose` line\n by putting a \\# on the front of it. That will save you having to\n find the file again by mistake. (Keyboard shortcut: go to the line,\n type control-shift-C or Mac equivalent with Cmd.)\n\n## Looking at what we read in\n\n- Again, type the name of the thing to display it:\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmydata\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 4\n id x y group\n \n1 p1 10 21 upper\n2 p2 11 20 lower\n3 p3 13 25 upper\n4 p4 15 27 lower\n5 p5 16 30 upper\n6 p6 17 31 lower\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- This is a \"tibble\" or data frame, the standard way of storing a data\n set in R.\n- Tibbles print as much as will display on the screen. If there are\n more rows or columns, it will say so.\n- You will see navigation keys to display more rows or columns (if\n there are more).\n\n## `View`-ing your data frame\n\n- Another way to examine your data frame is to View it, like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nView(mydata)\n```\n:::\n\n\n\n...or find your data frame in the Global Environment top right and click\nit. - This pops up a \"data frame viewer\" top left:\n\n![](viewview.png){height=\"500px\"}\n\n## This View\n\n- Read-only: cannot edit data\n- Can display data satisfying conditions: click on Filter, then:\n - for a categorical variable, type name of category you want\n - for a quantitative variable, use slider to describe values you\n want.\n- Can sort a column into ascending or descending order (click little\n arrows next to column name).\n- Clicking the symbol with arrow on it left of Filter \"pops out\" View\n into separate (bigger) window.\n\n## Summarizing what we read in\n\n- It is always a good idea to look at your data after you have read it\n in, to make sure you have believable numbers (and the right number\n of individuals and variables).\n- Quick check for errors: these often show up as values too high or\n too low, so the min and/or max will be unreasonable.\n- Five-number summary:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(mydata)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n id x y group \n Length:6 Min. :10.00 Min. :20.00 Length:6 \n Class :character 1st Qu.:11.50 1st Qu.:22.00 Class :character \n Mode :character Median :14.00 Median :26.00 Mode :character \n Mean :13.67 Mean :25.67 \n 3rd Qu.:15.75 3rd Qu.:29.25 \n Max. :17.00 Max. :31.00 \n```\n:::\n:::\n\n\n\n- Quantitative, five-number summary plus mean.\n- Categorical, how many rows.\n\n## Reading from a URL\n\n- Any data file on the Web can be read directly.\n- [Example data link:](http://ritsokiguess.site/datafiles/global.csv)\n- Use URL instead of filename.\n- I like to save the URL in a variable first (because URLs tend to be\n long), and then put that variable in the `read_` function:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/global.csv\"\nmy_url\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] \"http://ritsokiguess.site/datafiles/global.csv\"\n```\n:::\n\n```{.r .cell-code}\nglobal <- read_csv(my_url)\n```\n:::\n\n\n\n## The data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglobal\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 3\n warehouse size cost\n \n 1 A 225 12.0 \n 2 B 350 14.1 \n 3 A 150 8.93\n 4 A 200 11.0 \n 5 A 175 10.0 \n 6 A 180 10.1 \n 7 B 325 13.8 \n 8 B 290 13.3 \n 9 B 400 15 \n10 A 125 7.97\n```\n:::\n:::\n\n\n\n## Space-delimited files\n\n- Another common format for data is a text file with the values\n separated by spaces. Top of some other data:\n\n``` \ncup tempdiff \nStarbucks 13 \nStarbucks 7 \nStarbucks 7 \nStarbucks 17.5 \nStarbucks 10 \nStarbucks 15.5 \nStarbucks 6 \nStarbucks 6 \nSIGG 12 \nSIGG 16 \nSIGG 9 \nSIGG 23 \nSIGG 11 \nSIGG 20.5 \nSIGG 12.5 \nSIGG 20.5 \nSIGG 24.5 \nCUPPS 6 \nCUPPS 6 \nCUPPS 18.5 \nCUPPS 10 \n```\n\n## Reading the coffee data\n\n- This file was on my computer so I uploaded it to\n `r.datatools.utoronto.ca` first.\n- This time, `read_delim`, and we also have to say what the thing is\n separating the values:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee <- read_delim(\"coffee.txt\", \" \")\ncoffee\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 32 x 2\n cup tempdiff\n \n 1 Starbucks 13 \n 2 Starbucks 7 \n 3 Starbucks 7 \n 4 Starbucks 17.5\n 5 Starbucks 10 \n 6 Starbucks 15.5\n 7 Starbucks 6 \n 8 Starbucks 6 \n 9 SIGG 12 \n10 SIGG 16 \n# i 22 more rows\n```\n:::\n:::\n\n\n\n- Name of the cup, text, and tempdiff, a decimal number.\n\n## Looking at the values (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncoffee\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 32 x 2\n cup tempdiff\n \n 1 Starbucks 13 \n 2 Starbucks 7 \n 3 Starbucks 7 \n 4 Starbucks 17.5\n 5 Starbucks 10 \n 6 Starbucks 15.5\n 7 Starbucks 6 \n 8 Starbucks 6 \n 9 SIGG 12 \n10 SIGG 16 \n# i 22 more rows\n```\n:::\n:::\n\n\n\nThese were four brands of travel mug (in cup), and for each, how much\nthe temperature of the coffee in the mug decreased over 30 minutes.\n\n## Reading from the Web; the soap data\n\n- Use the URL in place of the filename.\n- Save the URL in a variable first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nurl <- \"http://ritsokiguess.site/datafiles/soap.txt\"\nsoap <- read_delim(url, \" \")\n```\n:::\n\n\n\n## The soap data (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsoap\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 27 x 4\n case scrap speed line \n \n 1 1 218 100 a \n 2 2 248 125 a \n 3 3 360 220 a \n 4 4 351 205 a \n 5 5 470 300 a \n 6 6 394 255 a \n 7 7 332 225 a \n 8 8 321 175 a \n 9 9 410 270 a \n10 10 260 170 a \n# i 17 more rows\n```\n:::\n:::\n\n\n\n## Data aligned in columns\n\n- Sometimes you see data aligned in columns, thus:\n\n![](Screenshot_2019-04-24_20-16-24.png){height=\"400px\"}\n\n- `read_delim` will not work: values separated by more than one space.\n- The number of spaces between values is not constant, because there\n is one fewer space before the 10.\n- `read_table` works for this.\n\n## Reading in column-aligned data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrugs <- read_table(\"migraine.txt\")\ndrugs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 9 x 3\n DrugA DrugB DrugC\n \n1 4 6 6\n2 5 8 7\n3 4 4 6\n4 3 5 6\n5 2 4 7\n6 4 6 5\n7 3 5 6\n8 4 10 5\n9 4 6 5\n```\n:::\n:::\n\n\n\n## Reading an Excel sheet directly\n\n- Here is [my spreadsheet](test2.xlsx) from before, but tarted up a\n bit:\n\n![](excel.png){height=\"450px\"}\n\n- It is now a workbook with a second sheet called \"notes\" (that we\n don't want).\n- Install package `readxl` first.\n\n\n\n::: {.cell}\n\n:::\n\n\n\n## Reading it in\n\n- Read into R, saying that we only want the sheet \"data\". Upload\n spreadsheet first.\n- Excel spreadsheets must be \"local\": cannot read one in from a URL.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# install.packages(\"readxl\")\nlibrary(readxl)\nmydata2 <- read_excel(\"test2.xlsx\", sheet = \"data\")\nmydata2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 4\n id x y group\n \n1 p1 10 21 upper\n2 p2 11 20 lower\n3 p3 13 25 upper\n4 p4 15 27 lower\n5 p5 16 30 upper\n6 p6 17 31 lower\n```\n:::\n:::\n", "supporting": [ "readfile_files" ], diff --git a/_freeze/regression/execute-results/html.json b/_freeze/regression/execute-results/html.json index 439df9d..70a71ea 100644 --- a/_freeze/regression/execute-results/html.json +++ b/_freeze/regression/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "0a5b234aee7fd30a3e7d7b6f92435e83", + "hash": "f509c8fa3b22f046f1f7782a0e23333b", "result": { - "markdown": "---\ntitle: \"Regression revisited\"\n---\n\n\n\n## Regression\n\n\n* Use regression when one variable is an outcome (*response*, $y$).\n\n* See if/how response depends on other variable(s), *explanatory*, $x_1, x_2,\\ldots$.\n\n* Can have *one* or *more than one* explanatory variable, but always one response.\n\n* Assumes a *straight-line* relationship between response and explanatory.\n\n* Ask: \n\n\n * *is there* a relationship between $y$ and $x$'s, and if so, which ones?\n * what does the relationship look like?\n\n\n\n\n## Packages\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS) # for Box-Cox, later\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\n```\n:::\n\n\n \n\n\n## A regression with one $x$\n\n13 children, measure average total sleep time (ATST, mins) and age (years) for each. See if ATST depends on age. Data in `sleep.txt`, ATST then age. Read in data:\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/sleep.txt\"\nsleep <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n\n## Check data \n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age \n Min. :461.8 Min. : 4.400 \n 1st Qu.:491.1 1st Qu.: 7.200 \n Median :528.3 Median : 8.900 \n Mean :519.3 Mean : 9.058 \n 3rd Qu.:532.5 3rd Qu.:11.100 \n Max. :586.0 Max. :14.000 \n```\n:::\n:::\n\n\nMake scatter plot of ATST (response) vs. age (explanatory) using\ncode overleaf: \n\n\n\n## The scatterplot\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/suggo-1.png){width=960}\n:::\n:::\n\n \n\n\n\n\n\n\n## Correlation\n\n\n* Measures how well a straight line fits the data:\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(sleep, cor(atst, age))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] -0.9515469\n```\n:::\n:::\n\n \n\n\n* $1$ is perfect upward trend, $-1$ is perfect downward trend, 0\nis no trend.\n\n* This one close to perfect downward trend.\n\n* Can do correlations of all pairs of variables:\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age\natst 1.0000000 -0.9515469\nage -0.9515469 1.0000000\n```\n:::\n:::\n\n \n\n\n## Lowess curve\n\n\n* Sometimes nice to guide the eye: is the trend straight, or not?\n\n* Idea: *lowess curve*. \"Locally weighted least squares\",\nnot affected by outliers, not constrained to be linear.\n\n* Lowess is a *guide*: even if straight line appropriate,\nmay wiggle/bend a little. Looking for *serious* problems with\nlinearity. \n\n* Add lowess curve to plot using `geom_smooth`:\n\n\n\n## Plot with lowess curve\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/icko-1.png){width=960}\n:::\n:::\n\n \n\n## The regression\n\nScatterplot shows no obvious curve, and a pretty clear downward trend. So we can run the regression:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 <- lm(atst ~ age, data = sleep)\n```\n:::\n\n \n## The output \n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = atst ~ age, data = sleep)\n\nResiduals:\n Min 1Q Median 3Q Max \n-23.011 -9.365 2.372 6.770 20.411 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 646.483 12.918 50.05 2.49e-14 ***\nage -14.041 1.368 -10.26 5.70e-07 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 13.15 on 11 degrees of freedom\nMultiple R-squared: 0.9054,\tAdjusted R-squared: 0.8968 \nF-statistic: 105.3 on 1 and 11 DF, p-value: 5.7e-07\n```\n:::\n:::\n\n\\normalsize\n\n\n\n## Conclusions\n\n\n* The relationship appears to be a straight line, with a downward trend.\n\n* $F$-tests for model as a whole and $t$-test for slope (same)\nboth confirm this (P-value $5.7\\times 10^{-7}=0.00000057$).\n\n* Slope is $-14$, so a 1-year increase in age goes with a 14-minute decrease in ATST on average.\n\n* R-squared is correlation squared (when one $x$ anyway),\nbetween 0 and 1 (1 good, 0 bad).\n\n* Here R-squared is 0.9054, pleasantly high.\n\n\n\n## Doing things with the regression output\n\n\n* Output from regression (and eg. $t$-test) is all right to\nlook at, but hard to extract and re-use information from.\n\n* Package `broom` extracts info from model output in way\nthat can be used in pipe (later):\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sleep.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## also one-line summary of model:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(sleep.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n\n## Broom part 2\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 %>% augment(sleep)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n \nUseful for plotting residuals against an $x$-variable.\n\n## CI for mean response and prediction intervals\n\nOnce useful regression exists, use it for prediction:\n\n\n* To get a single number for prediction at a given $x$, substitute into regression equation, eg. age 10: predicted ATST is $646.48-14.04(10)=506$ minutes.\n\n* To express uncertainty of this prediction:\n\n\n* *CI for mean response* expresses uncertainty about mean ATST for all children aged 10, based on data.\n\n* *Prediction interval* expresses uncertainty about predicted ATST for a new child aged 10 whose ATST not known. More uncertain.\n\n\n* Also do above for a child aged 5.\n\n\n## The `marginaleffects` package 1/2\n\nTo get predictions for specific values, set up a dataframe with those values first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sleep.1, age = c(10, 5))\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nAny variables in the dataframe that you don't specify are set to their mean values (quantitative) or most common category (categorical).\n\n## The `marginaleffects` package 2/2\n\nThen feed into `newdata` in `predictions`. This contains a lot of columns, so you probably want only to display the ones you care about:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sleep.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, age)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThe confidence limits are a 95% confidence interval for the mean response at that `age`.\n\n## Prediction intervals\n\nThese are obtained (instead) with `predict` as below. Use the same dataframe `new` as before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npp <- predict(sleep.1, new, interval = \"p\")\ncbind(new, pp) %>% select(-atst)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\n## Comments\n\n\n\n* Age 10 closer to centre of data, so intervals are both narrower than those for age 5.\n\n* Prediction intervals bigger than CI for mean (additional uncertainty).\n\n* Technical note: output from `predict` is R\n`matrix`, not data frame, so Tidyverse `bind_cols`\ndoes not work. Use base R `cbind`.\n\n\n\n## That grey envelope\n\nMarks confidence interval for mean for all $x$: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth(method = \"lm\") +\n scale_y_continuous(breaks = seq(420, 600, 20))\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-15-1.png){width=960}\n:::\n:::\n\n\n \n\n\n\n## Diagnostics\nHow to tell whether a straight-line regression is appropriate?\n\n\\vspace{3ex}\n\n\n\n* Before: check scatterplot for straight trend.\n\n* After: plot *residuals* (observed minus predicted response) against predicted values. Aim: a plot with no pattern.\n\n## Residual plot \n\nNot much pattern here --- regression appropriate.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/akjhkadjfhjahnkkk-1.png){width=960}\n:::\n:::\n\n \n\n\n\n\n## An inappropriate regression\n\nDifferent data: \n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/curvy.txt\"\ncurvy <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n## Scatterplot \n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy, aes(x = xx, y = yy)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-16-1.png){width=960}\n:::\n:::\n\n\n## Regression line, anyway\n\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.1 <- lm(yy ~ xx, data = curvy)\nsummary(curvy.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = yy ~ xx, data = curvy)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.582 -2.204 0.000 1.514 3.509 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 7.5818 1.5616 4.855 0.00126 **\nxx 0.9818 0.2925 3.356 0.00998 **\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.657 on 8 degrees of freedom\nMultiple R-squared: 0.5848,\tAdjusted R-squared: 0.5329 \nF-statistic: 11.27 on 1 and 8 DF, p-value: 0.009984\n```\n:::\n:::\n\n\\normalsize\n\n## Residual plot \n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/altoadige-1.png){width=960}\n:::\n:::\n\n \n\n\n\n## No good: fixing it up\n\n\n* Residual plot has *curve*: middle residuals positive, high and low ones negative. Bad.\n\n* Fitting a curve would be better. Try this:\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2 <- lm(yy ~ xx + I(xx^2), data = curvy)\n```\n:::\n\n \n\n\n* Adding `xx`-squared term, to allow for curve.\n\n* Another way to do same thing: specify how model *changes*:\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2a <- update(curvy.1, . ~ . + I(xx^2))\n```\n:::\n\n\n \n\n\n## Regression 2 \n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(curvy.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nglance(curvy.2) #\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Comments\n\n\n* `xx`-squared term definitely significant (P-value\n0.000182), so need this curve to describe relationship.\n\n* Adding squared term has made R-squared go up from 0.5848 to\n0.9502: great improvement.\n\n* This is a definite curve!\n\n\n\n## The residual plot now \nNo problems any more:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.2, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-21-1.png){width=960}\n:::\n:::\n\n \n\n\n\n\n\n## Another way to handle curves\n\n\n* Above, saw that changing $x$ (adding $x^2$) was a way of\nhandling curved relationships.\n\n* Another way: change $y$ (transformation).\n\n* Can guess how to change $y$, or might be theory:\n\n\n* example: relationship $y=ae^{bx}$ (exponential growth): \n\n* take\nlogs to get $\\ln y=\\ln a + bx$.\n\n* Taking logs has made relationship linear ($\\ln y$ as response).\n\n\n* Or, *estimate* transformation, using Box-Cox method. \n\n\n\n## Box-Cox\n\n\n* Install package `MASS` via\n`install.packages(\"MASS\")` (only need to do *once*)\n\n* Every R session you want to use something in `MASS`, type\n`library(MASS)`\n\n\n\n## Some made-up data\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/madeup2.csv\"\nmadeup <- read_csv(my_url)\nmadeup\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \nSeems to be faster-than-linear growth, maybe exponential growth. \n\n\n## Scatterplot: faster than linear growth \n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = y)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/dsljhsdjlhf-1.png){width=960}\n:::\n:::\n\n \n\n\n## Running Box-Cox\n\n\n* `library(MASS)` first.\n\n* Feed `boxcox` a model formula with a squiggle in it,\nsuch as you would use for `lm`.\n\n* Output: a graph (next page):\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(y ~ x, data = madeup)\n```\n:::\n\n \n\n\n\n## The Box-Cox output\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/trento-1.png){width=960}\n:::\n:::\n\n \n\n\n## Comments\n\n\n* $\\lambda$ (lambda) is the power by which you should transform\n$y$ to get the relationship straight (straighter). Power 0 is\n\"take logs\"\n\n* Middle dotted line marks best single value of $\\lambda$ (here\nabout 0.1).\n\n* Outer dotted lines mark 95\\% CI for $\\lambda$, here $-0.3$ to\n0.7, approx. (Rather uncertain about best transformation.)\n\n* Any power transformation within the CI supported by data. In\nthis case, log ($\\lambda=0$) and square root ($\\lambda=0.5$) good,\nbut no transformation ($\\lambda=1$) not.\n\n* Pick a \"round-number\" value of $\\lambda$ like\n$2,1,0.5,0,-0.5,-1$. Here 0 and 0.5 good values to pick. \n\n\n\n## Did transformation straighten things?\n\n* Plot transformed $y$ against $x$. Here, log:\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = log(y))) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-24-1.png){width=960}\n:::\n:::\n\n \n\nLooks much straighter.\n\n\n## Regression with transformed $y$\n\n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nmadeup.1 <- lm(log(y) ~ x, data = madeup)\nglance(madeup.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ntidy(madeup.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nR-squared now decently high.\n\n## Multiple regression\n\n\n* What if more than one $x$? Extra issues:\n\n\n * Now one intercept and a slope for each $x$: how to interpret?\n\n * Which $x$-variables actually help to predict $y$?\n\n * Different interpretations of \"global\" $F$-test and individual $t$-tests.\n\n * R-squared no longer correlation squared, but still\ninterpreted as \"higher better\".\n\n\n * In `lm` line, add extra $x$s after `~`.\n\n * Interpretation not so easy (and other problems that can occur).\n\n\n\n\n## Multiple regression example\n\nStudy of women and visits to health professionals, and how the number of visits might be related to other variables:\n\n\\begin{description}\n\\item[timedrs:] number of visits to health professionals (over course of study)\n\\item[phyheal:] number of physical health problems\n\\item[menheal:] number of mental health problems\n\\item[stress:] result of questionnaire about number and type of life changes\n\\end{description}\n\n`timedrs` response, others explanatory.\n\n\n\n## The data \n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/regressx.txt\"\nvisits <- read_delim(my_url, \" \")\n```\n:::\n\n \n\n\n## Check data\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n \n## Fit multiple regression\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.1 <- lm(timedrs ~ phyheal + menheal + stress,\n data = visits)\nsummary(visits.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-14.792 -4.353 -1.815 0.902 65.886 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -3.704848 1.124195 -3.296 0.001058 ** \nphyheal 1.786948 0.221074 8.083 5.6e-15 ***\nmenheal -0.009666 0.129029 -0.075 0.940318 \nstress 0.013615 0.003612 3.769 0.000185 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 9.708 on 461 degrees of freedom\nMultiple R-squared: 0.2188,\tAdjusted R-squared: 0.2137 \nF-statistic: 43.03 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n## The slopes\n\n- Model as a whole strongly significant even though R-sq not very big (lots of data). At least one of the $x$'s predicts `timedrs`.\n\n\n\n- The physical health and stress variables definitely help to predict the number of visits, but *with those in the model* we don't need `menheal`.\nHowever, look at prediction of `timedrs` from `menheal` by itself:\n\n\n## Just `menheal` \n\n\\footnotesize \n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.2 <- lm(timedrs ~ menheal, data = visits)\nsummary(visits.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ menheal, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-13.826 -5.150 -2.818 1.177 72.513 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 3.8159 0.8702 4.385 1.44e-05 ***\nmenheal 0.6672 0.1173 5.688 2.28e-08 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 10.6 on 463 degrees of freedom\nMultiple R-squared: 0.06532,\tAdjusted R-squared: 0.0633 \nF-statistic: 32.35 on 1 and 463 DF, p-value: 2.279e-08\n```\n:::\n:::\n\n \n\\normalsize\n\n\n\n## `menheal` by itself\n\n\n* `menheal` by itself *does* significantly help to predict `timedrs`.\n\n* But the R-sq is much less (6.5\\% vs.\\ 22\\%).\n\n* So other two variables do a better job of prediction.\n\n* With those variables in the regression (`phyheal` and\n`stress`), don't need `menheal` *as well*.\n\n\n## Investigating via correlation\nLeave out first column (`subjno`):\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits %>% select(-subjno) %>% cor()\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n timedrs phyheal menheal stress\ntimedrs 1.0000000 0.4395293 0.2555703 0.2865951\nphyheal 0.4395293 1.0000000 0.5049464 0.3055517\nmenheal 0.2555703 0.5049464 1.0000000 0.3697911\nstress 0.2865951 0.3055517 0.3697911 1.0000000\n```\n:::\n:::\n\n \n\n\n* `phyheal` most strongly correlated with `timedrs`.\n\n* Not much to choose between other two.\n\n* But `menheal` has higher correlation with `phyheal`,\nso not as much to *add* to prediction as `stress`.\n\n* Goes to show things more complicated in multiple regression.\n\n\n\n\n## Residual plot (from `timedrs` on all) \n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/iffy8-1.png){width=960}\n:::\n:::\n\n \n\nApparently random. But...\n\n\n\n\n\n## Normal quantile plot of residuals\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-32-1.png){width=960}\n:::\n:::\n\n\nNot normal at all; upper tail is way too long. \n\n\n## Absolute residuals\nIs there trend in *size* of residuals (fan-out)? Plot\n*absolute value* of residual against fitted value:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-33-1.png){width=960}\n:::\n:::\n\n\n\n\n\n## Comments\n\n\n* On the normal quantile plot:\n\n\n * highest (most positive) residuals are *way* too high\n\n * distribution of residuals skewed to right (not normal at all)\n\n\n* On plot of absolute residuals:\n\n\n * size of residuals getting bigger as fitted values increase\n\n * predictions getting more variable as fitted values increase\n\n * that is, predictions getting *less accurate* as fitted\nvalues increase, but predictions should be equally accurate all\nway along.\n\n\n* Both indicate problems with regression, of kind that\ntransformation of response often fixes: that is, predict\n*function* of response `timedrs` instead of\n`timedrs` itself.\n\n\n\n\n## Box-Cox transformations\n\n\n* Taking log of `timedrs` and having it work: lucky\nguess. How to find good transformation?\n\n* Box-Cox again.\n\n* Extra problem: some of `timedrs` values are 0, but\nBox-Cox expects all +. Note response for `boxcox`:\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress, data = visits)\n```\n:::\n\n \n\n\n\n## Try 1\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-36-1.png){width=960}\n:::\n:::\n\n \n\n\n## Comments on try 1\n\n\n* Best: $\\lambda$ just less than zero.\n\n* Hard to see scale. \n\n* Focus on $\\lambda$ in $(-0.3,0.1)$: \n\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nmy.lambda <- seq(-0.3, 0.1, 0.01)\nmy.lambda\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] -0.30 -0.29 -0.28 -0.27 -0.26 -0.25 -0.24 -0.23 -0.22\n[10] -0.21 -0.20 -0.19 -0.18 -0.17 -0.16 -0.15 -0.14 -0.13\n[19] -0.12 -0.11 -0.10 -0.09 -0.08 -0.07 -0.06 -0.05 -0.04\n[28] -0.03 -0.02 -0.01 0.00 0.01 0.02 0.03 0.04 0.05\n[37] 0.06 0.07 0.08 0.09 0.10\n```\n:::\n:::\n\n \n\\normalsize\n\n\n## Try 2\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress,\n lambda = my.lambda,\n data = visits\n)\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-38-1.png){width=960}\n:::\n:::\n\n \n\n\n## Comments\n\n\n* Best: $\\lambda$ just about $-0.07$.\n\n* CI for $\\lambda$ about $(-0.14,0.01)$.\n\n* Only nearby round number: $\\lambda=0$, log transformation.\n\n\n## Fixing the problems \n\n\n* Try regression again, with transformed response instead of\noriginal one.\n\n* Then check residual plot to see that it is OK now.\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.3 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress,\n data = visits\n)\n```\n:::\n\n \n\n* `timedrs+1` because some `timedrs` values 0,\ncan't take log of 0.\n\n* Won't usually need to worry about this, but when response could\nbe zero/negative, fix that before transformation.\n\n\n## Output \n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(visits.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(timedrs + 1) ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-1.95865 -0.44076 -0.02331 0.42304 2.36797 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.3903862 0.0882908 4.422 1.22e-05 ***\nphyheal 0.2019361 0.0173624 11.631 < 2e-16 ***\nmenheal 0.0071442 0.0101335 0.705 0.481 \nstress 0.0013158 0.0002837 4.638 4.58e-06 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.7625 on 461 degrees of freedom\nMultiple R-squared: 0.3682,\tAdjusted R-squared: 0.3641 \nF-statistic: 89.56 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n \n\\normalsize\n\n\n## Comments \n\n\n* Model as a whole strongly significant again \n\n* R-sq higher than before (37\\% vs.\\ 22\\%) suggesting things more linear now\n\n* Same conclusion re `menheal`: can take out of regression.\n\n* Should look at residual plots (next pages). Have we fixed problems?\n\n\n\n## Residuals against fitted values\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-41-1.png){width=960}\n:::\n:::\n\n\n \n\n\n## Normal quantile plot of residuals\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-42-1.png){width=960}\n:::\n:::\n\n\n \n\n\n## Absolute residuals against fitted\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-43-1.png){width=960}\n:::\n:::\n\n\n \n\n\n## Comments \n\n\n* Residuals vs.\\ fitted looks a lot more random.\n\n* Normal quantile plot looks a lot more normal (though still a\nlittle right-skewness)\n\n* Absolute residuals: not so much trend (though still some).\n\n* Not perfect, but much improved.\n\n\n\n## Testing more than one $x$ at once\n\n- The $t$-tests test only whether one variable could be taken out of the\nregression you're looking at. \n- To test significance of more than one\nvariable at once, fit model with and without variables \n - then use `anova` to compare fit of models:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.5 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress, \n data = visits)\nvisits.6 <- lm(log(timedrs + 1) ~ stress, data = visits)\n```\n:::\n\n \n\n\n## Results of tests\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(visits.6, visits.5)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n* Models don't fit equally well, so bigger one fits better.\n\n* Or \"taking both variables out makes the fit worse, so don't do it\".\n\n* Taking out those $x$'s\nis a mistake. Or putting them in is a good idea.\n\n\n\n## The punting data\nData set `punting.txt` contains 4 variables for 13 right-footed\nfootball kickers (punters): left leg and right leg strength (lbs),\ndistance punted (ft), another variable called \"fred\". Predict\npunting distance from other variables:\n\n\\scriptsize\n\n```\nleft right punt fred\n170 170 162.50 171 \n130 140 144.0 136 \n170 180 174.50 174 \n160 160 163.50 161 \n150 170 192.0 159 \n150 150 171.75 151 \n180 170 162.0 174 \n110 110 104.83 111 \n110 120 105.67 114 \n120 130 117.58 126 \n140 120 140.25 129 \n130 140 150.17 136 \n150 160 165.17 154 \n\n```\n\\normalsize\n\n\n## Reading in\n\n\n* Separated by *multiple spaces* with *columns lined up*:\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/punting.txt\"\npunting <- read_table(my_url)\n```\n:::\n\n\n \n\n\n\n## The data\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\npunting\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n \n\n\n## Regression and output\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.1 <- lm(punt ~ left + right + fred, data = punting)\nglance(punting.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ntidy(punting.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Comments\n\n\n* Overall regression strongly significant, R-sq high.\n\n* None of the $x$'s significant! Why?\n\n* $t$-tests only say that you could take any one of the $x$'s out without damaging the fit; doesn't matter which one.\n\n* Explanation: look at *correlations*. \n\n\n\n## The correlations \n\n::: {.cell}\n\n```{.r .cell-code}\ncor(punting)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n left right punt fred\nleft 1.0000000 0.8957224 0.8117368 0.9722632\nright 0.8957224 1.0000000 0.8805469 0.9728784\npunt 0.8117368 0.8805469 1.0000000 0.8679507\nfred 0.9722632 0.9728784 0.8679507 1.0000000\n```\n:::\n:::\n\n \n\n\n* *All* correlations are high: $x$'s with `punt` (good) and\nwith each other (bad, at least confusing).\n\n* What to do? Probably do just as well to pick one variable, say\n`right` since kickers are right-footed.\n\n\n\n## Just `right`\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 <- lm(punt ~ right, data = punting)\nanova(punting.2, punting.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n\\normalsize\nNo significant loss by dropping other two variables.\n\n\n\n## Comparing R-squareds\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.1)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7781401\n```\n:::\n\n```{.r .cell-code}\nsummary(punting.2)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7753629\n```\n:::\n:::\n\n \nBasically no difference. In regression (over), `right` significant:\n\n\n## Regression results\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(punting.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n \n## But\\ldots\n\n\n* Maybe we got the *form* of the relationship with\n`left` wrong.\n\n* Check: plot *residuals* from previous regression (without\n`left`) against `left`.\n\n* Residuals here are \"punting distance adjusted for right\nleg strength\".\n\n* If there is some kind of relationship with `left`, we\nshould include in model.\n\n* Plot of residuals against original variable: `augment`\nfrom `broom`.\n\n\n\n## Augmenting `punting.2`\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 %>% augment(punting) -> punting.2.aug\npunting.2.aug \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n \n\n\n## Residuals against `left`\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(punting.2.aug, aes(x = left, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/basingstoke-1.png){width=960}\n:::\n:::\n\n\n \n\n\n## Comments\n\n\n* There is a *curved* relationship with `left`.\n\n* We should add `left`-squared to the regression (and\ntherefore put `left` back in when we do that):\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.3 <- lm(punt ~ left + I(left^2) + right,\n data = punting\n)\n```\n:::\n\n\n \n\n\n\n## Regression with `left-squared`\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left + I(left^2) + right, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-11.3777 -5.3599 0.0459 4.5088 13.2669 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -4.623e+02 9.902e+01 -4.669 0.00117 **\nleft 6.888e+00 1.462e+00 4.710 0.00110 **\nI(left^2) -2.302e-02 4.927e-03 -4.672 0.00117 **\nright 7.396e-01 2.292e-01 3.227 0.01038 * \n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 7.931 on 9 degrees of freedom\nMultiple R-squared: 0.9352,\tAdjusted R-squared: 0.9136 \nF-statistic: 43.3 on 3 and 9 DF, p-value: 1.13e-05\n```\n:::\n:::\n\n\\normalsize\n \n\n\n## Comments\n\n\n* This was definitely a good idea (R-squared has clearly increased).\n\n* We would never have seen it without plotting residuals from\n`punting.2` (without `left`) against `left`.\n\n* Negative slope for `leftsq` means that increased left-leg\nstrength only increases punting distance up to a point: beyond that,\nit decreases again.\n\n\n \n\n\n", + "markdown": "---\ntitle: \"Regression revisited\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Regression\n\n- Use regression when one variable is an outcome (*response*, $y$).\n\n- See if/how response depends on other variable(s), *explanatory*,\n $x_1, x_2,\\ldots$.\n\n- Can have *one* or *more than one* explanatory variable, but always\n one response.\n\n- Assumes a *straight-line* relationship between response and\n explanatory.\n\n- Ask:\n\n - *is there* a relationship between $y$ and $x$'s, and if so,\n which ones?\n - what does the relationship look like?\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS) # for Box-Cox, later\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\n```\n:::\n\n\n## A regression with one $x$\n\n13 children, measure average total sleep time (ATST, mins) and age\n(years) for each. See if ATST depends on age. Data in `sleep.txt`, ATST\nthen age. Read in data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/sleep.txt\"\nsleep <- read_delim(my_url, \" \")\n```\n:::\n\n\n## Check data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age \n Min. :461.8 Min. : 4.400 \n 1st Qu.:491.1 1st Qu.: 7.200 \n Median :528.3 Median : 8.900 \n Mean :519.3 Mean : 9.058 \n 3rd Qu.:532.5 3rd Qu.:11.100 \n Max. :586.0 Max. :14.000 \n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nMake scatter plot of ATST (response) vs. age (explanatory) using code\noverleaf:\n\n## The scatterplot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/suggo-1.png){width=960}\n:::\n:::\n\n\n## Correlation\n\n- Measures how well a straight line fits the data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(sleep, cor(atst, age))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] -0.9515469\n```\n:::\n:::\n\n\n- $1$ is perfect upward trend, $-1$ is perfect downward trend, 0 is no\n trend.\n\n- This one close to perfect downward trend.\n\n- Can do correlations of all pairs of variables:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age\natst 1.0000000 -0.9515469\nage -0.9515469 1.0000000\n```\n:::\n:::\n\n\n## Lowess curve\n\n- Sometimes nice to guide the eye: is the trend straight, or not?\n\n- Idea: *lowess curve*. \"Locally weighted least squares\", not affected\n by outliers, not constrained to be linear.\n\n- Lowess is a *guide*: even if straight line appropriate, may\n wiggle/bend a little. Looking for *serious* problems with linearity.\n\n- Add lowess curve to plot using `geom_smooth`:\n\n## Plot with lowess curve\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/icko-1.png){width=960}\n:::\n:::\n\n\n## The regression\n\nScatterplot shows no obvious curve, and a pretty clear downward trend.\nSo we can run the regression:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 <- lm(atst ~ age, data = sleep)\n```\n:::\n\n\n## The output\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = atst ~ age, data = sleep)\n\nResiduals:\n Min 1Q Median 3Q Max \n-23.011 -9.365 2.372 6.770 20.411 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 646.483 12.918 50.05 2.49e-14 ***\nage -14.041 1.368 -10.26 5.70e-07 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 13.15 on 11 degrees of freedom\nMultiple R-squared: 0.9054,\tAdjusted R-squared: 0.8968 \nF-statistic: 105.3 on 1 and 11 DF, p-value: 5.7e-07\n```\n:::\n:::\n\n\n## Conclusions\n\n- The relationship appears to be a straight line, with a downward\n trend.\n\n- $F$-tests for model as a whole and $t$-test for slope (same) both\n confirm this (P-value $5.7\\times 10^{-7}=0.00000057$).\n\n- Slope is $-14$, so a 1-year increase in age goes with a 14-minute\n decrease in ATST on average.\n\n- R-squared is correlation squared (when one $x$ anyway), between 0\n and 1 (1 good, 0 bad).\n\n- Here R-squared is 0.9054, pleasantly high.\n\n## Doing things with the regression output\n\n- Output from regression (and eg. $t$-test) is all right to look at,\n but hard to extract and re-use information from.\n\n- Package `broom` extracts info from model output in way that can be\n used in pipe (later):\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sleep.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## also one-line summary of model:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(sleep.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Broom part 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 %>% augment(sleep)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nUseful for plotting residuals against an $x$-variable.\n\n## CI for mean response and prediction intervals\n\nOnce useful regression exists, use it for prediction:\n\n- To get a single number for prediction at a given $x$, substitute\n into regression equation, eg. age 10: predicted ATST is\n $646.48-14.04(10)=506$ minutes.\n\n- To express uncertainty of this prediction:\n\n- *CI for mean response* expresses uncertainty about mean ATST for all\n children aged 10, based on data.\n\n- *Prediction interval* expresses uncertainty about predicted ATST for\n a new child aged 10 whose ATST not known. More uncertain.\n\n- Also do above for a child aged 5.\n\n## The `marginaleffects` package 1/2\n\nTo get predictions for specific values, set up a dataframe with those\nvalues first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sleep.1, age = c(10, 5))\nnew\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nAny variables in the dataframe that you don't specify are set to their\nmean values (quantitative) or most common category (categorical).\n\n## The `marginaleffects` package 2/2\n\nThen feed into `newdata` in `predictions`. This contains a lot of\ncolumns, so you probably want only to display the ones you care about:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sleep.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, age)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThe confidence limits are a 95% confidence interval for the mean\nresponse at that `age`.\n\n## Prediction intervals\n\nThese are obtained (instead) with `predict` as below. Use the same\ndataframe `new` as before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npp <- predict(sleep.1, new, interval = \"p\")\npp\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n fit lwr upr\n1 506.0729 475.8982 536.2475\n2 576.2781 543.8474 608.7088\n```\n:::\n\n```{.r .cell-code}\ncbind(new, pp) %>% select(-atst)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- Age 10 closer to centre of data, so intervals are both narrower than\n those for age 5.\n\n- Prediction intervals bigger than CI for mean (additional\n uncertainty).\n\n- Technical note: output from `predict` is R `matrix`, not data frame,\n so Tidyverse `bind_cols` does not work. Use base R `cbind`.\n\n## That grey envelope\n\nMarks confidence interval for mean for all $x$:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth(method = \"lm\") +\n scale_y_continuous(breaks = seq(420, 600, 20))\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-15-1.png){width=960}\n:::\n:::\n\n\n## Diagnostics\n\nHow to tell whether a straight-line regression is appropriate?\n\n- Before: check scatterplot for straight trend.\n\n- After: plot *residuals* (observed minus predicted response) against\n predicted values. Aim: a plot with no pattern.\n\n## Residual plot\n\nNot much pattern here --- regression appropriate.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/akjhkadjfhjahnkkk-1.png){width=960}\n:::\n:::\n\n\n## An inappropriate regression\n\nDifferent data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/curvy.txt\"\ncurvy <- read_delim(my_url, \" \")\n```\n:::\n\n\n## Scatterplot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy, aes(x = xx, y = yy)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-16-1.png){width=960}\n:::\n:::\n\n\n## Regression line, anyway\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.1 <- lm(yy ~ xx, data = curvy)\nsummary(curvy.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = yy ~ xx, data = curvy)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.582 -2.204 0.000 1.514 3.509 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 7.5818 1.5616 4.855 0.00126 **\nxx 0.9818 0.2925 3.356 0.00998 **\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.657 on 8 degrees of freedom\nMultiple R-squared: 0.5848,\tAdjusted R-squared: 0.5329 \nF-statistic: 11.27 on 1 and 8 DF, p-value: 0.009984\n```\n:::\n:::\n\n\n## Residual plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/altoadige-1.png){width=960}\n:::\n:::\n\n\n## No good: fixing it up\n\n- Residual plot has *curve*: middle residuals positive, high and low\n ones negative. Bad.\n\n- Fitting a curve would be better. Try this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2 <- lm(yy ~ xx + I(xx^2), data = curvy)\n```\n:::\n\n\n- Adding `xx`-squared term, to allow for curve.\n\n- Another way to do same thing: specify how model *changes*:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2a <- update(curvy.1, . ~ . + I(xx^2))\n```\n:::\n\n\n## Regression 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(curvy.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nglance(curvy.2) #\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- `xx`-squared term definitely significant (P-value 0.000182), so need\n this curve to describe relationship.\n\n- Adding squared term has made R-squared go up from 0.5848 to 0.9502:\n great improvement.\n\n- This is a definite curve!\n\n## The residual plot now\n\nNo problems any more:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.2, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-21-1.png){width=960}\n:::\n:::\n\n\n## Another way to handle curves\n\n- Above, saw that changing $x$ (adding $x^2$) was a way of handling\n curved relationships.\n\n- Another way: change $y$ (transformation).\n\n- Can guess how to change $y$, or might be theory:\n\n- example: relationship $y=ae^{bx}$ (exponential growth):\n\n- take logs to get $\\ln y=\\ln a + bx$.\n\n- Taking logs has made relationship linear ($\\ln y$ as response).\n\n- Or, *estimate* transformation, using Box-Cox method.\n\n## Box-Cox\n\n- Install package `MASS` via `install.packages(\"MASS\")` (only need to\n do *once*)\n\n- Every R session you want to use something in `MASS`, type\n `library(MASS)`\n\n## Some made-up data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/madeup2.csv\"\nmadeup <- read_csv(my_url)\nmadeup\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nSeems to be faster-than-linear growth, maybe exponential growth.\n\n## Scatterplot: faster than linear growth\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = y)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/dsljhsdjlhf-1.png){width=960}\n:::\n:::\n\n\n## Running Box-Cox\n\n- `library(MASS)` first.\n\n- Feed `boxcox` a model formula with a squiggle in it, such as you\n would use for `lm`.\n\n- Output: a graph (next page):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(y ~ x, data = madeup)\n```\n:::\n\n\n## The Box-Cox output\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/trento-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- $\\lambda$ (lambda) is the power by which you should transform $y$ to\n get the relationship straight (straighter). Power 0 is \"take logs\"\n\n- Middle dotted line marks best single value of $\\lambda$ (here about\n 0.1).\n\n- Outer dotted lines mark 95% CI for $\\lambda$, here $-0.3$ to 0.7,\n approx. (Rather uncertain about best transformation.)\n\n- Any power transformation within the CI supported by data. In this\n case, log ($\\lambda=0$) and square root ($\\lambda=0.5$) good, but no\n transformation ($\\lambda=1$) not.\n\n- Pick a \"round-number\" value of $\\lambda$ like $2,1,0.5,0,-0.5,-1$.\n Here 0 and 0.5 good values to pick.\n\n## Did transformation straighten things?\n\n- Plot transformed $y$ against $x$. Here, log:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = log(y))) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-24-1.png){width=960}\n:::\n:::\n\n\nLooks much straighter.\n\n## Regression with transformed $y$\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmadeup.1 <- lm(log(y) ~ x, data = madeup)\nglance(madeup.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ntidy(madeup.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nR-squared now decently high.\n\n## Multiple regression\n\n- What if more than one $x$? Extra issues:\n\n - Now one intercept and a slope for each $x$: how to interpret?\n\n - Which $x$-variables actually help to predict $y$?\n\n - Different interpretations of \"global\" $F$-test and individual\n $t$-tests.\n\n - R-squared no longer correlation squared, but still interpreted\n as \"higher better\".\n\n - In `lm` line, add extra $x$s after `~`.\n\n - Interpretation not so easy (and other problems that can occur).\n\n## Multiple regression example\n\nStudy of women and visits to health professionals, and how the number of\nvisits might be related to other variables:\n\n\n```{=tex}\n\\begin{description}\n\\item[timedrs:] number of visits to health professionals (over course of study)\n\\item[phyheal:] number of physical health problems\n\\item[menheal:] number of mental health problems\n\\item[stress:] result of questionnaire about number and type of life changes\n\\end{description}\n```\n\n`timedrs` response, others explanatory.\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/regressx.txt\"\nvisits <- read_delim(my_url, \" \")\n```\n:::\n\n\n## Check data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Fit multiple regression\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.1 <- lm(timedrs ~ phyheal + menheal + stress,\n data = visits)\nsummary(visits.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-14.792 -4.353 -1.815 0.902 65.886 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -3.704848 1.124195 -3.296 0.001058 ** \nphyheal 1.786948 0.221074 8.083 5.6e-15 ***\nmenheal -0.009666 0.129029 -0.075 0.940318 \nstress 0.013615 0.003612 3.769 0.000185 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 9.708 on 461 degrees of freedom\nMultiple R-squared: 0.2188,\tAdjusted R-squared: 0.2137 \nF-statistic: 43.03 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n## The slopes\n\n- Model as a whole strongly significant even though R-sq not very big\n (lots of data). At least one of the $x$'s predicts `timedrs`.\n\n- The physical health and stress variables definitely help to predict\n the number of visits, but *with those in the model* we don't need\n `menheal`. However, look at prediction of `timedrs` from `menheal`\n by itself:\n\n## Just `menheal`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.2 <- lm(timedrs ~ menheal, data = visits)\nsummary(visits.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ menheal, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-13.826 -5.150 -2.818 1.177 72.513 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 3.8159 0.8702 4.385 1.44e-05 ***\nmenheal 0.6672 0.1173 5.688 2.28e-08 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 10.6 on 463 degrees of freedom\nMultiple R-squared: 0.06532,\tAdjusted R-squared: 0.0633 \nF-statistic: 32.35 on 1 and 463 DF, p-value: 2.279e-08\n```\n:::\n:::\n\n\n## `menheal` by itself\n\n- `menheal` by itself *does* significantly help to predict `timedrs`.\n\n- But the R-sq is much less (6.5% vs. 22%).\n\n- So other two variables do a better job of prediction.\n\n- With those variables in the regression (`phyheal` and `stress`),\n don't need `menheal` *as well*.\n\n## Investigating via correlation\n\nLeave out first column (`subjno`):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits %>% select(-subjno) %>% cor()\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n timedrs phyheal menheal stress\ntimedrs 1.0000000 0.4395293 0.2555703 0.2865951\nphyheal 0.4395293 1.0000000 0.5049464 0.3055517\nmenheal 0.2555703 0.5049464 1.0000000 0.3697911\nstress 0.2865951 0.3055517 0.3697911 1.0000000\n```\n:::\n:::\n\n\n- `phyheal` most strongly correlated with `timedrs`.\n\n- Not much to choose between other two.\n\n- But `menheal` has higher correlation with `phyheal`, so not as much\n to *add* to prediction as `stress`.\n\n- Goes to show things more complicated in multiple regression.\n\n## Residual plot (from `timedrs` on all)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/iffy8-1.png){width=960}\n:::\n:::\n\n\nApparently random. But...\n\n## Normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-32-1.png){width=960}\n:::\n:::\n\n\nNot normal at all; upper tail is way too long.\n\n## Absolute residuals\n\nIs there trend in *size* of residuals (fan-out)? Plot *absolute value*\nof residual against fitted value:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-33-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- On the normal quantile plot:\n\n - highest (most positive) residuals are *way* too high\n\n - distribution of residuals skewed to right (not normal at all)\n\n- On plot of absolute residuals:\n\n - size of residuals getting bigger as fitted values increase\n\n - predictions getting more variable as fitted values increase\n\n - that is, predictions getting *less accurate* as fitted values\n increase, but predictions should be equally accurate all way\n along.\n\n- Both indicate problems with regression, of kind that transformation\n of response often fixes: that is, predict *function* of response\n `timedrs` instead of `timedrs` itself.\n\n## Box-Cox transformations\n\n- Taking log of `timedrs` and having it work: lucky guess. How to find\n good transformation?\n\n- Box-Cox again.\n\n- Extra problem: some of `timedrs` values are 0, but Box-Cox expects\n all +. Note response for `boxcox`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress, data = visits)\n```\n:::\n\n\n## Try 1\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-36-1.png){width=960}\n:::\n:::\n\n\n## Comments on try 1\n\n- Best: $\\lambda$ just less than zero.\n\n- Hard to see scale.\n\n- Focus on $\\lambda$ in $(-0.3,0.1)$:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy.lambda <- seq(-0.3, 0.1, 0.01)\nmy.lambda\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] -0.30 -0.29 -0.28 -0.27 -0.26 -0.25 -0.24 -0.23 -0.22\n[10] -0.21 -0.20 -0.19 -0.18 -0.17 -0.16 -0.15 -0.14 -0.13\n[19] -0.12 -0.11 -0.10 -0.09 -0.08 -0.07 -0.06 -0.05 -0.04\n[28] -0.03 -0.02 -0.01 0.00 0.01 0.02 0.03 0.04 0.05\n[37] 0.06 0.07 0.08 0.09 0.10\n```\n:::\n:::\n\n\n## Try 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress,\n lambda = my.lambda,\n data = visits\n)\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-38-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Best: $\\lambda$ just about $-0.07$.\n\n- CI for $\\lambda$ about $(-0.14,0.01)$.\n\n- Only nearby round number: $\\lambda=0$, log transformation.\n\n## Fixing the problems\n\n- Try regression again, with transformed response instead of original\n one.\n\n- Then check residual plot to see that it is OK now.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.3 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress,\n data = visits\n)\n```\n:::\n\n\n- `timedrs+1` because some `timedrs` values 0, can't take log of 0.\n\n- Won't usually need to worry about this, but when response could be\n zero/negative, fix that before transformation.\n\n## Output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(visits.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(timedrs + 1) ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-1.95865 -0.44076 -0.02331 0.42304 2.36797 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.3903862 0.0882908 4.422 1.22e-05 ***\nphyheal 0.2019361 0.0173624 11.631 < 2e-16 ***\nmenheal 0.0071442 0.0101335 0.705 0.481 \nstress 0.0013158 0.0002837 4.638 4.58e-06 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.7625 on 461 degrees of freedom\nMultiple R-squared: 0.3682,\tAdjusted R-squared: 0.3641 \nF-statistic: 89.56 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n## Comments\n\n- Model as a whole strongly significant again\n\n- R-sq higher than before (37% vs. 22%) suggesting things more linear\n now\n\n- Same conclusion re `menheal`: can take out of regression.\n\n- Should look at residual plots (next pages). Have we fixed problems?\n\n## Residuals against fitted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-41-1.png){width=960}\n:::\n:::\n\n\n## Normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-42-1.png){width=960}\n:::\n:::\n\n\n## Absolute residuals against fitted\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/bRegression-43-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Residuals vs. fitted looks a lot more random.\n\n- Normal quantile plot looks a lot more normal (though still a little\n right-skewness)\n\n- Absolute residuals: not so much trend (though still some).\n\n- Not perfect, but much improved.\n\n## Testing more than one $x$ at once\n\n- The $t$-tests test only whether one variable could be taken out of\n the regression you're looking at.\n- To test significance of more than one variable at once, fit model\n with and without variables\n - then use `anova` to compare fit of models:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.5 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress, \n data = visits)\nvisits.6 <- lm(log(timedrs + 1) ~ stress, data = visits)\n```\n:::\n\n\n## Results of tests\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(visits.6, visits.5)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Models don't fit equally well, so bigger one fits better.\n\n- Or \"taking both variables out makes the fit worse, so don't do it\".\n\n- Taking out those $x$'s is a mistake. Or putting them in is a good\n idea.\n\n## The punting data\n\nData set `punting.txt` contains 4 variables for 13 right-footed football\nkickers (punters): left leg and right leg strength (lbs), distance\npunted (ft), another variable called \"fred\". Predict punting distance\nfrom other variables:\n\n\\scriptsize\n\n``` \nleft right punt fred\n170 170 162.50 171 \n130 140 144.0 136 \n170 180 174.50 174 \n160 160 163.50 161 \n150 170 192.0 159 \n150 150 171.75 151 \n180 170 162.0 174 \n110 110 104.83 111 \n110 120 105.67 114 \n120 130 117.58 126 \n140 120 140.25 129 \n130 140 150.17 136 \n150 160 165.17 154 \n```\n\n## Reading in\n\n- Separated by *multiple spaces* with *columns lined up*:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/punting.txt\"\npunting <- read_table(my_url)\n```\n:::\n\n\n## The data\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Regression and output\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.1 <- lm(punt ~ left + right + fred, data = punting)\nglance(punting.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\ntidy(punting.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nsummary(punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left + right + fred, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-14.9325 -11.5618 -0.0315 9.0415 20.0886 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|)\n(Intercept) -4.6855 29.1172 -0.161 0.876\nleft 0.2679 2.1111 0.127 0.902\nright 1.0524 2.1477 0.490 0.636\nfred -0.2672 4.2266 -0.063 0.951\n\nResidual standard error: 14.68 on 9 degrees of freedom\nMultiple R-squared: 0.7781,\tAdjusted R-squared: 0.7042 \nF-statistic: 10.52 on 3 and 9 DF, p-value: 0.00267\n```\n:::\n:::\n\n\n## Comments\n\n- Overall regression strongly significant, R-sq high.\n\n- None of the $x$'s significant! Why?\n\n- $t$-tests only say that you could take any one of the $x$'s out\n without damaging the fit; doesn't matter which one.\n\n- Explanation: look at *correlations*.\n\n## The correlations\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(punting)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n left right punt fred\nleft 1.0000000 0.8957224 0.8117368 0.9722632\nright 0.8957224 1.0000000 0.8805469 0.9728784\npunt 0.8117368 0.8805469 1.0000000 0.8679507\nfred 0.9722632 0.9728784 0.8679507 1.0000000\n```\n:::\n:::\n\n\n- *All* correlations are high: $x$'s with `punt` (good) and with each\n other (bad, at least confusing).\n\n- What to do? Probably do just as well to pick one variable, say\n `right` since kickers are right-footed.\n\n## Just `right`\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 <- lm(punt ~ right, data = punting)\nsummary(punting.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ right, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-15.7576 -11.0611 0.3656 7.8890 19.0423 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -3.6930 25.2649 -0.146 0.886 \nright 1.0427 0.1692 6.162 7.09e-05 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 13.36 on 11 degrees of freedom\nMultiple R-squared: 0.7754,\tAdjusted R-squared: 0.7549 \nF-statistic: 37.97 on 1 and 11 DF, p-value: 7.088e-05\n```\n:::\n\n```{.r .cell-code}\nanova(punting.2, punting.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\npunting.3 <- lm(punt ~ left, data = punting)\nsummary(punting.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-22.840 -12.298 -2.234 8.990 35.820 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 12.8834 30.1575 0.427 0.677474 \nleft 0.9553 0.2072 4.610 0.000753 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 16.46 on 11 degrees of freedom\nMultiple R-squared: 0.6589,\tAdjusted R-squared: 0.6279 \nF-statistic: 21.25 on 1 and 11 DF, p-value: 0.0007528\n```\n:::\n:::\n\n\nNo significant loss by dropping other two variables.\n\n## Comparing R-squareds\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.1)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7781401\n```\n:::\n\n```{.r .cell-code}\nsummary(punting.2)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7753629\n```\n:::\n:::\n\n\nBasically no difference. In regression (over), `right` significant:\n\n## Regression results\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(punting.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## But\\ldots\n\n- Maybe we got the *form* of the relationship with `left` wrong.\n\n- Check: plot *residuals* from previous regression (without `left`)\n against `left`.\n\n- Residuals here are \"punting distance adjusted for right leg\n strength\".\n\n- If there is some kind of relationship with `left`, we should include\n in model.\n\n- Plot of residuals against original variable: `augment` from `broom`.\n\n## Augmenting `punting.2`\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 %>% augment(punting) -> punting.2.aug\npunting.2.aug \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Residuals against `left`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(punting.2.aug, aes(x = left, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-revealjs/basingstoke-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- There is a *curved* relationship with `left`.\n\n- We should add `left`-squared to the regression (and therefore put\n `left` back in when we do that):\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.3 <- lm(punt ~ left + I(left^2) + right,\n data = punting\n)\n```\n:::\n\n\n## Regression with `left-squared`\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left + I(left^2) + right, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-11.3777 -5.3599 0.0459 4.5088 13.2669 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -4.623e+02 9.902e+01 -4.669 0.00117 **\nleft 6.888e+00 1.462e+00 4.710 0.00110 **\nI(left^2) -2.302e-02 4.927e-03 -4.672 0.00117 **\nright 7.396e-01 2.292e-01 3.227 0.01038 * \n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 7.931 on 9 degrees of freedom\nMultiple R-squared: 0.9352,\tAdjusted R-squared: 0.9136 \nF-statistic: 43.3 on 3 and 9 DF, p-value: 1.13e-05\n```\n:::\n:::\n\n\n## Comments\n\n- This was definitely a good idea (R-squared has clearly increased).\n\n- We would never have seen it without plotting residuals from\n `punting.2` (without `left`) against `left`.\n\n- Negative slope for `leftsq` means that increased left-leg strength\n only increases punting distance up to a point: beyond that, it\n decreases again.\n", "supporting": [ "regression_files/figure-revealjs" ], diff --git a/_freeze/regression/execute-results/tex.json b/_freeze/regression/execute-results/tex.json index c76d2ec..f776224 100644 --- a/_freeze/regression/execute-results/tex.json +++ b/_freeze/regression/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "0a5b234aee7fd30a3e7d7b6f92435e83", + "hash": "f509c8fa3b22f046f1f7782a0e23333b", "result": { - "markdown": "---\ntitle: \"Regression revisited\"\n---\n\n\n\n\n## Regression\n\n\n* Use regression when one variable is an outcome (*response*, $y$).\n\n* See if/how response depends on other variable(s), *explanatory*, $x_1, x_2,\\ldots$.\n\n* Can have *one* or *more than one* explanatory variable, but always one response.\n\n* Assumes a *straight-line* relationship between response and explanatory.\n\n* Ask: \n\n\n * *is there* a relationship between $y$ and $x$'s, and if so, which ones?\n * what does the relationship look like?\n\n\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS) # for Box-Cox, later\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\n```\n:::\n\n\n\n \n\n\n## A regression with one $x$\n\n13 children, measure average total sleep time (ATST, mins) and age (years) for each. See if ATST depends on age. Data in `sleep.txt`, ATST then age. Read in data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/sleep.txt\"\nsleep <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n\n\n## Check data \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age \n Min. :461.8 Min. : 4.400 \n 1st Qu.:491.1 1st Qu.: 7.200 \n Median :528.3 Median : 8.900 \n Mean :519.3 Mean : 9.058 \n 3rd Qu.:532.5 3rd Qu.:11.100 \n Max. :586.0 Max. :14.000 \n```\n:::\n:::\n\n\n\nMake scatter plot of ATST (response) vs. age (explanatory) using\ncode overleaf: \n\n\n\n## The scatterplot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/suggo-1.pdf)\n:::\n:::\n\n\n \n\n\n\n\n\n\n## Correlation\n\n\n* Measures how well a straight line fits the data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(sleep, cor(atst, age))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] -0.9515469\n```\n:::\n:::\n\n\n \n\n\n* $1$ is perfect upward trend, $-1$ is perfect downward trend, 0\nis no trend.\n\n* This one close to perfect downward trend.\n\n* Can do correlations of all pairs of variables:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age\natst 1.0000000 -0.9515469\nage -0.9515469 1.0000000\n```\n:::\n:::\n\n\n \n\n\n## Lowess curve\n\n\n* Sometimes nice to guide the eye: is the trend straight, or not?\n\n* Idea: *lowess curve*. \"Locally weighted least squares\",\nnot affected by outliers, not constrained to be linear.\n\n* Lowess is a *guide*: even if straight line appropriate,\nmay wiggle/bend a little. Looking for *serious* problems with\nlinearity. \n\n* Add lowess curve to plot using `geom_smooth`:\n\n\n\n## Plot with lowess curve\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/icko-1.pdf)\n:::\n:::\n\n\n \n\n## The regression\n\nScatterplot shows no obvious curve, and a pretty clear downward trend. So we can run the regression:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 <- lm(atst ~ age, data = sleep)\n```\n:::\n\n\n \n## The output \n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = atst ~ age, data = sleep)\n\nResiduals:\n Min 1Q Median 3Q Max \n-23.011 -9.365 2.372 6.770 20.411 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 646.483 12.918 50.05 2.49e-14 ***\nage -14.041 1.368 -10.26 5.70e-07 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 13.15 on 11 degrees of freedom\nMultiple R-squared: 0.9054,\tAdjusted R-squared: 0.8968 \nF-statistic: 105.3 on 1 and 11 DF, p-value: 5.7e-07\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n## Conclusions\n\n\n* The relationship appears to be a straight line, with a downward trend.\n\n* $F$-tests for model as a whole and $t$-test for slope (same)\nboth confirm this (P-value $5.7\\times 10^{-7}=0.00000057$).\n\n* Slope is $-14$, so a 1-year increase in age goes with a 14-minute decrease in ATST on average.\n\n* R-squared is correlation squared (when one $x$ anyway),\nbetween 0 and 1 (1 good, 0 bad).\n\n* Here R-squared is 0.9054, pleasantly high.\n\n\n\n## Doing things with the regression output\n\n\n* Output from regression (and eg. $t$-test) is all right to\nlook at, but hard to extract and re-use information from.\n\n* Package `broom` extracts info from model output in way\nthat can be used in pipe (later):\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 646. 12.9 50.0 2.49e-14\n2 age -14.0 1.37 -10.3 5.70e- 7\n```\n:::\n:::\n\n\n\n## also one-line summary of model:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.905 0.897 13.2 105. 0.000000570 1\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\n\n\n## Broom part 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 %>% augment(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 8\n atst age .fitted .resid .hat .sigma .cooksd\n \n 1 586 4.4 585. 1.30 0.312 13.8 0.00320\n 2 462. 14 450. 11.8 0.341 13.0 0.319 \n 3 491. 10.1 505. -13.6 0.0887 13.0 0.0568 \n 4 565 6.7 552. 12.6 0.137 13.1 0.0844 \n 5 462 11.5 485. -23.0 0.141 11.3 0.294 \n 6 532. 9.6 512. 20.4 0.0801 12.0 0.114 \n 7 478. 12.4 472. 5.23 0.198 13.7 0.0243 \n 8 515. 8.9 522. -6.32 0.0772 13.6 0.0105 \n 9 493 11.1 491. 2.37 0.122 13.8 0.00258\n10 528. 7.75 538. -9.37 0.0954 13.4 0.0296 \n11 576. 5.5 569. 6.64 0.214 13.6 0.0441 \n12 532. 8.6 526. 6.77 0.0792 13.6 0.0124 \n13 530. 7.2 545. -14.9 0.114 12.9 0.0933 \n# i 1 more variable: .std.resid \n```\n:::\n:::\n\n\n\n \nUseful for plotting residuals against an $x$-variable.\n\n## CI for mean response and prediction intervals\n\nOnce useful regression exists, use it for prediction:\n\n\n* To get a single number for prediction at a given $x$, substitute into regression equation, eg. age 10: predicted ATST is $646.48-14.04(10)=506$ minutes.\n\n* To express uncertainty of this prediction:\n\n\n* *CI for mean response* expresses uncertainty about mean ATST for all children aged 10, based on data.\n\n* *Prediction interval* expresses uncertainty about predicted ATST for a new child aged 10 whose ATST not known. More uncertain.\n\n\n* Also do above for a child aged 5.\n\n\n## The `marginaleffects` package 1/2\n\nTo get predictions for specific values, set up a dataframe with those values first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sleep.1, age = c(10, 5))\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age\n1 519.3038 10\n2 519.3038 5\n```\n:::\n:::\n\n\n\nAny variables in the dataframe that you don't specify are set to their mean values (quantitative) or most common category (categorical).\n\n## The `marginaleffects` package 2/2\n\nThen feed into `newdata` in `predictions`. This contains a lot of columns, so you probably want only to display the ones you care about:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sleep.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, age)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate conf.low conf.high age\n1 506.0729 498.4899 513.6558 10\n2 576.2781 563.2588 589.2974 5\n```\n:::\n:::\n\n\n\nThe confidence limits are a 95% confidence interval for the mean response at that `age`.\n\n## Prediction intervals\n\nThese are obtained (instead) with `predict` as below. Use the same dataframe `new` as before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npp <- predict(sleep.1, new, interval = \"p\")\ncbind(new, pp) %>% select(-atst)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n age fit lwr upr\n1 10 506.0729 475.8982 536.2475\n2 5 576.2781 543.8474 608.7088\n```\n:::\n:::\n\n\n \n\n## Comments\n\n\n\n* Age 10 closer to centre of data, so intervals are both narrower than those for age 5.\n\n* Prediction intervals bigger than CI for mean (additional uncertainty).\n\n* Technical note: output from `predict` is R\n`matrix`, not data frame, so Tidyverse `bind_cols`\ndoes not work. Use base R `cbind`.\n\n\n\n## That grey envelope\n\nMarks confidence interval for mean for all $x$: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth(method = \"lm\") +\n scale_y_continuous(breaks = seq(420, 600, 20))\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-15-1.pdf)\n:::\n:::\n\n\n\n \n\n\n\n## Diagnostics\nHow to tell whether a straight-line regression is appropriate?\n\n\\vspace{3ex}\n\n\n\n* Before: check scatterplot for straight trend.\n\n* After: plot *residuals* (observed minus predicted response) against predicted values. Aim: a plot with no pattern.\n\n## Residual plot \n\nNot much pattern here --- regression appropriate.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/akjhkadjfhjahnkkk-1.pdf)\n:::\n:::\n\n\n \n\n\n\n\n## An inappropriate regression\n\nDifferent data: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/curvy.txt\"\ncurvy <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n\n## Scatterplot \n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy, aes(x = xx, y = yy)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-16-1.pdf)\n:::\n:::\n\n\n\n## Regression line, anyway\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.1 <- lm(yy ~ xx, data = curvy)\nsummary(curvy.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = yy ~ xx, data = curvy)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.582 -2.204 0.000 1.514 3.509 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 7.5818 1.5616 4.855 0.00126 **\nxx 0.9818 0.2925 3.356 0.00998 **\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.657 on 8 degrees of freedom\nMultiple R-squared: 0.5848,\tAdjusted R-squared: 0.5329 \nF-statistic: 11.27 on 1 and 8 DF, p-value: 0.009984\n```\n:::\n:::\n\n\n\\normalsize\n\n## Residual plot \n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/altoadige-1.pdf)\n:::\n:::\n\n\n \n\n\n\n## No good: fixing it up\n\n\n* Residual plot has *curve*: middle residuals positive, high and low ones negative. Bad.\n\n* Fitting a curve would be better. Try this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2 <- lm(yy ~ xx + I(xx^2), data = curvy)\n```\n:::\n\n\n \n\n\n* Adding `xx`-squared term, to allow for curve.\n\n* Another way to do same thing: specify how model *changes*:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2a <- update(curvy.1, . ~ . + I(xx^2))\n```\n:::\n\n\n\n \n\n\n## Regression 2 \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(curvy.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 3.9 0.773 5.04 0.00149 \n2 xx 3.74 0.400 9.36 0.0000331\n3 I(xx^2) -0.307 0.0428 -7.17 0.000182 \n```\n:::\n\n```{.r .cell-code}\nglance(curvy.2) #\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.950 0.936 0.983 66.8 0.0000275 2\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\n## Comments\n\n\n* `xx`-squared term definitely significant (P-value\n0.000182), so need this curve to describe relationship.\n\n* Adding squared term has made R-squared go up from 0.5848 to\n0.9502: great improvement.\n\n* This is a definite curve!\n\n\n\n## The residual plot now \nNo problems any more:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.2, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-21-1.pdf)\n:::\n:::\n\n\n \n\n\n\n\n\n## Another way to handle curves\n\n\n* Above, saw that changing $x$ (adding $x^2$) was a way of\nhandling curved relationships.\n\n* Another way: change $y$ (transformation).\n\n* Can guess how to change $y$, or might be theory:\n\n\n* example: relationship $y=ae^{bx}$ (exponential growth): \n\n* take\nlogs to get $\\ln y=\\ln a + bx$.\n\n* Taking logs has made relationship linear ($\\ln y$ as response).\n\n\n* Or, *estimate* transformation, using Box-Cox method. \n\n\n\n## Box-Cox\n\n\n* Install package `MASS` via\n`install.packages(\"MASS\")` (only need to do *once*)\n\n* Every R session you want to use something in `MASS`, type\n`library(MASS)`\n\n\n\n## Some made-up data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/madeup2.csv\"\nmadeup <- read_csv(my_url)\nmadeup\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 3\n ...1 x y\n \n1 1 0 17.9\n2 2 1 33.6\n3 3 2 82.7\n4 4 3 31.2\n5 5 4 177. \n6 6 5 359. \n7 7 6 469. \n8 8 7 283. \n```\n:::\n:::\n\n\n \nSeems to be faster-than-linear growth, maybe exponential growth. \n\n\n## Scatterplot: faster than linear growth \n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = y)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/dsljhsdjlhf-1.pdf)\n:::\n:::\n\n\n \n\n\n## Running Box-Cox\n\n\n* `library(MASS)` first.\n\n* Feed `boxcox` a model formula with a squiggle in it,\nsuch as you would use for `lm`.\n\n* Output: a graph (next page):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(y ~ x, data = madeup)\n```\n:::\n\n\n \n\n\n\n## The Box-Cox output\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-beamer/trento-1.pdf)\n:::\n:::\n\n\n \n\n\n## Comments\n\n\n* $\\lambda$ (lambda) is the power by which you should transform\n$y$ to get the relationship straight (straighter). Power 0 is\n\"take logs\"\n\n* Middle dotted line marks best single value of $\\lambda$ (here\nabout 0.1).\n\n* Outer dotted lines mark 95\\% CI for $\\lambda$, here $-0.3$ to\n0.7, approx. (Rather uncertain about best transformation.)\n\n* Any power transformation within the CI supported by data. In\nthis case, log ($\\lambda=0$) and square root ($\\lambda=0.5$) good,\nbut no transformation ($\\lambda=1$) not.\n\n* Pick a \"round-number\" value of $\\lambda$ like\n$2,1,0.5,0,-0.5,-1$. Here 0 and 0.5 good values to pick. \n\n\n\n## Did transformation straighten things?\n\n* Plot transformed $y$ against $x$. Here, log:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = log(y))) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-24-1.pdf)\n:::\n:::\n\n\n \n\nLooks much straighter.\n\n\n## Regression with transformed $y$\n\n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmadeup.1 <- lm(log(y) ~ x, data = madeup)\nglance(madeup.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.811 0.779 0.588 25.7 0.00228 1\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n\n```{.r .cell-code}\ntidy(madeup.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 3.03 0.379 7.98 0.000206\n2 x 0.460 0.0907 5.07 0.00228 \n```\n:::\n:::\n\n\n\\normalsize\n\nR-squared now decently high.\n\n## Multiple regression\n\n\n* What if more than one $x$? Extra issues:\n\n\n * Now one intercept and a slope for each $x$: how to interpret?\n\n * Which $x$-variables actually help to predict $y$?\n\n * Different interpretations of \"global\" $F$-test and individual $t$-tests.\n\n * R-squared no longer correlation squared, but still\ninterpreted as \"higher better\".\n\n\n * In `lm` line, add extra $x$s after `~`.\n\n * Interpretation not so easy (and other problems that can occur).\n\n\n\n\n## Multiple regression example\n\nStudy of women and visits to health professionals, and how the number of visits might be related to other variables:\n\n\\begin{description}\n\\item[timedrs:] number of visits to health professionals (over course of study)\n\\item[phyheal:] number of physical health problems\n\\item[menheal:] number of mental health problems\n\\item[stress:] result of questionnaire about number and type of life changes\n\\end{description}\n\n`timedrs` response, others explanatory.\n\n\n\n## The data \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/regressx.txt\"\nvisits <- read_delim(my_url, \" \")\n```\n:::\n\n\n \n\n\n## Check data\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 465 x 5\n subjno timedrs phyheal menheal stress\n \n 1 1 1 5 8 265\n 2 2 3 4 6 415\n 3 3 0 3 4 92\n 4 4 13 2 2 241\n 5 5 15 3 6 86\n 6 6 3 5 5 247\n 7 7 2 5 6 13\n 8 8 0 4 5 12\n 9 9 7 5 4 269\n10 10 4 3 9 391\n# i 455 more rows\n```\n:::\n:::\n\n\n\\normalsize\n\n \n## Fit multiple regression\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.1 <- lm(timedrs ~ phyheal + menheal + stress,\n data = visits)\nsummary(visits.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-14.792 -4.353 -1.815 0.902 65.886 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -3.704848 1.124195 -3.296 0.001058 ** \nphyheal 1.786948 0.221074 8.083 5.6e-15 ***\nmenheal -0.009666 0.129029 -0.075 0.940318 \nstress 0.013615 0.003612 3.769 0.000185 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 9.708 on 461 degrees of freedom\nMultiple R-squared: 0.2188,\tAdjusted R-squared: 0.2137 \nF-statistic: 43.03 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n\n## The slopes\n\n- Model as a whole strongly significant even though R-sq not very big (lots of data). At least one of the $x$'s predicts `timedrs`.\n\n\n\n- The physical health and stress variables definitely help to predict the number of visits, but *with those in the model* we don't need `menheal`.\nHowever, look at prediction of `timedrs` from `menheal` by itself:\n\n\n## Just `menheal` \n\n\\footnotesize \n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.2 <- lm(timedrs ~ menheal, data = visits)\nsummary(visits.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ menheal, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-13.826 -5.150 -2.818 1.177 72.513 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 3.8159 0.8702 4.385 1.44e-05 ***\nmenheal 0.6672 0.1173 5.688 2.28e-08 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 10.6 on 463 degrees of freedom\nMultiple R-squared: 0.06532,\tAdjusted R-squared: 0.0633 \nF-statistic: 32.35 on 1 and 463 DF, p-value: 2.279e-08\n```\n:::\n:::\n\n\n \n\\normalsize\n\n\n\n## `menheal` by itself\n\n\n* `menheal` by itself *does* significantly help to predict `timedrs`.\n\n* But the R-sq is much less (6.5\\% vs.\\ 22\\%).\n\n* So other two variables do a better job of prediction.\n\n* With those variables in the regression (`phyheal` and\n`stress`), don't need `menheal` *as well*.\n\n\n## Investigating via correlation\nLeave out first column (`subjno`):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits %>% select(-subjno) %>% cor()\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n timedrs phyheal menheal stress\ntimedrs 1.0000000 0.4395293 0.2555703 0.2865951\nphyheal 0.4395293 1.0000000 0.5049464 0.3055517\nmenheal 0.2555703 0.5049464 1.0000000 0.3697911\nstress 0.2865951 0.3055517 0.3697911 1.0000000\n```\n:::\n:::\n\n\n \n\n\n* `phyheal` most strongly correlated with `timedrs`.\n\n* Not much to choose between other two.\n\n* But `menheal` has higher correlation with `phyheal`,\nso not as much to *add* to prediction as `stress`.\n\n* Goes to show things more complicated in multiple regression.\n\n\n\n\n## Residual plot (from `timedrs` on all) \n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/iffy8-1.pdf)\n:::\n:::\n\n\n \n\nApparently random. But...\n\n\n\n\n\n## Normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-32-1.pdf)\n:::\n:::\n\n\n\nNot normal at all; upper tail is way too long. \n\n\n## Absolute residuals\nIs there trend in *size* of residuals (fan-out)? Plot\n*absolute value* of residual against fitted value:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-33-1.pdf)\n:::\n:::\n\n\n\n\n\n\n## Comments\n\n\n* On the normal quantile plot:\n\n\n * highest (most positive) residuals are *way* too high\n\n * distribution of residuals skewed to right (not normal at all)\n\n\n* On plot of absolute residuals:\n\n\n * size of residuals getting bigger as fitted values increase\n\n * predictions getting more variable as fitted values increase\n\n * that is, predictions getting *less accurate* as fitted\nvalues increase, but predictions should be equally accurate all\nway along.\n\n\n* Both indicate problems with regression, of kind that\ntransformation of response often fixes: that is, predict\n*function* of response `timedrs` instead of\n`timedrs` itself.\n\n\n\n\n## Box-Cox transformations\n\n\n* Taking log of `timedrs` and having it work: lucky\nguess. How to find good transformation?\n\n* Box-Cox again.\n\n* Extra problem: some of `timedrs` values are 0, but\nBox-Cox expects all +. Note response for `boxcox`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress, data = visits)\n```\n:::\n\n\n \n\n\n\n## Try 1\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-36-1.pdf)\n:::\n:::\n\n\n \n\n\n## Comments on try 1\n\n\n* Best: $\\lambda$ just less than zero.\n\n* Hard to see scale. \n\n* Focus on $\\lambda$ in $(-0.3,0.1)$: \n\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy.lambda <- seq(-0.3, 0.1, 0.01)\nmy.lambda\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] -0.30 -0.29 -0.28 -0.27 -0.26 -0.25 -0.24 -0.23 -0.22\n[10] -0.21 -0.20 -0.19 -0.18 -0.17 -0.16 -0.15 -0.14 -0.13\n[19] -0.12 -0.11 -0.10 -0.09 -0.08 -0.07 -0.06 -0.05 -0.04\n[28] -0.03 -0.02 -0.01 0.00 0.01 0.02 0.03 0.04 0.05\n[37] 0.06 0.07 0.08 0.09 0.10\n```\n:::\n:::\n\n\n \n\\normalsize\n\n\n## Try 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress,\n lambda = my.lambda,\n data = visits\n)\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-38-1.pdf)\n:::\n:::\n\n\n \n\n\n## Comments\n\n\n* Best: $\\lambda$ just about $-0.07$.\n\n* CI for $\\lambda$ about $(-0.14,0.01)$.\n\n* Only nearby round number: $\\lambda=0$, log transformation.\n\n\n## Fixing the problems \n\n\n* Try regression again, with transformed response instead of\noriginal one.\n\n* Then check residual plot to see that it is OK now.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.3 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress,\n data = visits\n)\n```\n:::\n\n\n \n\n* `timedrs+1` because some `timedrs` values 0,\ncan't take log of 0.\n\n* Won't usually need to worry about this, but when response could\nbe zero/negative, fix that before transformation.\n\n\n## Output \n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(visits.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(timedrs + 1) ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-1.95865 -0.44076 -0.02331 0.42304 2.36797 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.3903862 0.0882908 4.422 1.22e-05 ***\nphyheal 0.2019361 0.0173624 11.631 < 2e-16 ***\nmenheal 0.0071442 0.0101335 0.705 0.481 \nstress 0.0013158 0.0002837 4.638 4.58e-06 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.7625 on 461 degrees of freedom\nMultiple R-squared: 0.3682,\tAdjusted R-squared: 0.3641 \nF-statistic: 89.56 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n \n\\normalsize\n\n\n## Comments \n\n\n* Model as a whole strongly significant again \n\n* R-sq higher than before (37\\% vs.\\ 22\\%) suggesting things more linear now\n\n* Same conclusion re `menheal`: can take out of regression.\n\n* Should look at residual plots (next pages). Have we fixed problems?\n\n\n\n## Residuals against fitted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-41-1.pdf)\n:::\n:::\n\n\n\n \n\n\n## Normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-42-1.pdf)\n:::\n:::\n\n\n\n \n\n\n## Absolute residuals against fitted\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-43-1.pdf)\n:::\n:::\n\n\n\n \n\n\n## Comments \n\n\n* Residuals vs.\\ fitted looks a lot more random.\n\n* Normal quantile plot looks a lot more normal (though still a\nlittle right-skewness)\n\n* Absolute residuals: not so much trend (though still some).\n\n* Not perfect, but much improved.\n\n\n\n## Testing more than one $x$ at once\n\n- The $t$-tests test only whether one variable could be taken out of the\nregression you're looking at. \n- To test significance of more than one\nvariable at once, fit model with and without variables \n - then use `anova` to compare fit of models:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.5 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress, \n data = visits)\nvisits.6 <- lm(log(timedrs + 1) ~ stress, data = visits)\n```\n:::\n\n\n \n\n\n## Results of tests\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(visits.6, visits.5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nModel 1: log(timedrs + 1) ~ stress\nModel 2: log(timedrs + 1) ~ phyheal + menheal + stress\n Res.Df RSS Df Sum of Sq F Pr(>F) \n1 463 371.47 \n2 461 268.01 2 103.46 88.984 < 2.2e-16 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n\n* Models don't fit equally well, so bigger one fits better.\n\n* Or \"taking both variables out makes the fit worse, so don't do it\".\n\n* Taking out those $x$'s\nis a mistake. Or putting them in is a good idea.\n\n\n\n## The punting data\nData set `punting.txt` contains 4 variables for 13 right-footed\nfootball kickers (punters): left leg and right leg strength (lbs),\ndistance punted (ft), another variable called \"fred\". Predict\npunting distance from other variables:\n\n\\scriptsize\n\n```\nleft right punt fred\n170 170 162.50 171 \n130 140 144.0 136 \n170 180 174.50 174 \n160 160 163.50 161 \n150 170 192.0 159 \n150 150 171.75 151 \n180 170 162.0 174 \n110 110 104.83 111 \n110 120 105.67 114 \n120 130 117.58 126 \n140 120 140.25 129 \n130 140 150.17 136 \n150 160 165.17 154 \n\n```\n\\normalsize\n\n\n## Reading in\n\n\n* Separated by *multiple spaces* with *columns lined up*:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/punting.txt\"\npunting <- read_table(my_url)\n```\n:::\n\n\n\n \n\n\n\n## The data\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 4\n left right punt fred\n \n 1 170 170 162. 171\n 2 130 140 144 136\n 3 170 180 174. 174\n 4 160 160 164. 161\n 5 150 170 192 159\n 6 150 150 172. 151\n 7 180 170 162 174\n 8 110 110 105. 111\n 9 110 120 106. 114\n10 120 130 118. 126\n11 140 120 140. 129\n12 130 140 150. 136\n13 150 160 165. 154\n```\n:::\n:::\n\n\n\\normalsize\n \n\n\n## Regression and output\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.1 <- lm(punt ~ left + right + fred, data = punting)\nglance(punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.778 0.704 14.7 10.5 0.00267 3\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n\n```{.r .cell-code}\ntidy(punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -4.69 29.1 -0.161 0.876\n2 left 0.268 2.11 0.127 0.902\n3 right 1.05 2.15 0.490 0.636\n4 fred -0.267 4.23 -0.0632 0.951\n```\n:::\n:::\n\n\n\\normalsize\n\n## Comments\n\n\n* Overall regression strongly significant, R-sq high.\n\n* None of the $x$'s significant! Why?\n\n* $t$-tests only say that you could take any one of the $x$'s out without damaging the fit; doesn't matter which one.\n\n* Explanation: look at *correlations*. \n\n\n\n## The correlations \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(punting)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n left right punt fred\nleft 1.0000000 0.8957224 0.8117368 0.9722632\nright 0.8957224 1.0000000 0.8805469 0.9728784\npunt 0.8117368 0.8805469 1.0000000 0.8679507\nfred 0.9722632 0.9728784 0.8679507 1.0000000\n```\n:::\n:::\n\n\n \n\n\n* *All* correlations are high: $x$'s with `punt` (good) and\nwith each other (bad, at least confusing).\n\n* What to do? Probably do just as well to pick one variable, say\n`right` since kickers are right-footed.\n\n\n\n## Just `right`\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 <- lm(punt ~ right, data = punting)\nanova(punting.2, punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nModel 1: punt ~ right\nModel 2: punt ~ left + right + fred\n Res.Df RSS Df Sum of Sq F Pr(>F)\n1 11 1962.5 \n2 9 1938.2 2 24.263 0.0563 0.9456\n```\n:::\n:::\n\n\n \n\\normalsize\nNo significant loss by dropping other two variables.\n\n\n\n## Comparing R-squareds\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.1)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7781401\n```\n:::\n\n```{.r .cell-code}\nsummary(punting.2)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7753629\n```\n:::\n:::\n\n\n \nBasically no difference. In regression (over), `right` significant:\n\n\n## Regression results\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(punting.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -3.69 25.3 -0.146 0.886 \n2 right 1.04 0.169 6.16 0.0000709\n```\n:::\n:::\n\n\n \n## But\\ldots\n\n\n* Maybe we got the *form* of the relationship with\n`left` wrong.\n\n* Check: plot *residuals* from previous regression (without\n`left`) against `left`.\n\n* Residuals here are \"punting distance adjusted for right\nleg strength\".\n\n* If there is some kind of relationship with `left`, we\nshould include in model.\n\n* Plot of residuals against original variable: `augment`\nfrom `broom`.\n\n\n\n## Augmenting `punting.2`\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 %>% augment(punting) -> punting.2.aug\npunting.2.aug \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 10\n left right punt fred .fitted .resid .hat .sigma\n \n 1 170 170 162. 171 174. -11.1 0.157 13.5\n 2 130 140 144 136 142. 1.72 0.0864 14.0\n 3 170 180 174. 174 184. -9.49 0.244 13.6\n 4 160 160 164. 161 163. 0.366 0.101 14.0\n 5 150 170 192 159 174. 18.4 0.157 12.5\n 6 150 150 172. 151 153. 19.0 0.0778 12.5\n 7 180 170 162 174 174. -11.6 0.157 13.4\n 8 110 110 105. 111 111. -6.17 0.305 13.8\n 9 110 120 106. 114 121. -15.8 0.2 12.9\n10 120 130 118. 126 132. -14.3 0.127 13.1\n11 140 120 140. 129 121. 18.8 0.2 12.3\n12 130 140 150. 136 142. 7.89 0.0864 13.8\n13 150 160 165. 154 163. 2.04 0.101 14.0\n# i 2 more variables: .cooksd , .std.resid \n```\n:::\n:::\n\n\n\\normalsize\n \n\n\n## Residuals against `left`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(punting.2.aug, aes(x = left, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/basingstoke-1.pdf)\n:::\n:::\n\n\n\n \n\n\n## Comments\n\n\n* There is a *curved* relationship with `left`.\n\n* We should add `left`-squared to the regression (and\ntherefore put `left` back in when we do that):\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.3 <- lm(punt ~ left + I(left^2) + right,\n data = punting\n)\n```\n:::\n\n\n\n \n\n\n\n## Regression with `left-squared`\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left + I(left^2) + right, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-11.3777 -5.3599 0.0459 4.5088 13.2669 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -4.623e+02 9.902e+01 -4.669 0.00117 **\nleft 6.888e+00 1.462e+00 4.710 0.00110 **\nI(left^2) -2.302e-02 4.927e-03 -4.672 0.00117 **\nright 7.396e-01 2.292e-01 3.227 0.01038 * \n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 7.931 on 9 degrees of freedom\nMultiple R-squared: 0.9352,\tAdjusted R-squared: 0.9136 \nF-statistic: 43.3 on 3 and 9 DF, p-value: 1.13e-05\n```\n:::\n:::\n\n\n\\normalsize\n \n\n\n## Comments\n\n\n* This was definitely a good idea (R-squared has clearly increased).\n\n* We would never have seen it without plotting residuals from\n`punting.2` (without `left`) against `left`.\n\n* Negative slope for `leftsq` means that increased left-leg\nstrength only increases punting distance up to a point: beyond that,\nit decreases again.\n\n\n \n\n\n", + "markdown": "---\ntitle: \"Regression revisited\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Regression\n\n- Use regression when one variable is an outcome (*response*, $y$).\n\n- See if/how response depends on other variable(s), *explanatory*,\n $x_1, x_2,\\ldots$.\n\n- Can have *one* or *more than one* explanatory variable, but always\n one response.\n\n- Assumes a *straight-line* relationship between response and\n explanatory.\n\n- Ask:\n\n - *is there* a relationship between $y$ and $x$'s, and if so,\n which ones?\n - what does the relationship look like?\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(MASS) # for Box-Cox, later\nlibrary(tidyverse)\nlibrary(broom)\nlibrary(marginaleffects)\nlibrary(conflicted)\nconflict_prefer(\"select\", \"dplyr\")\n```\n:::\n\n\n\n## A regression with one $x$\n\n13 children, measure average total sleep time (ATST, mins) and age\n(years) for each. See if ATST depends on age. Data in `sleep.txt`, ATST\nthen age. Read in data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/sleep.txt\"\nsleep <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n## Check data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age \n Min. :461.8 Min. : 4.400 \n 1st Qu.:491.1 1st Qu.: 7.200 \n Median :528.3 Median : 8.900 \n Mean :519.3 Mean : 9.058 \n 3rd Qu.:532.5 3rd Qu.:11.100 \n Max. :586.0 Max. :14.000 \n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 2\n atst age\n \n 1 586 4.4 \n 2 462. 14 \n 3 491. 10.1 \n 4 565 6.7 \n 5 462 11.5 \n 6 532. 9.6 \n 7 478. 12.4 \n 8 515. 8.9 \n 9 493 11.1 \n10 528. 7.75\n11 576. 5.5 \n12 532. 8.6 \n13 530. 7.2 \n```\n:::\n:::\n\n\n\nMake scatter plot of ATST (response) vs. age (explanatory) using code\noverleaf:\n\n## The scatterplot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/suggo-1.pdf)\n:::\n:::\n\n\n\n## Correlation\n\n- Measures how well a straight line fits the data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(sleep, cor(atst, age))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] -0.9515469\n```\n:::\n:::\n\n\n\n- $1$ is perfect upward trend, $-1$ is perfect downward trend, 0 is no\n trend.\n\n- This one close to perfect downward trend.\n\n- Can do correlations of all pairs of variables:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age\natst 1.0000000 -0.9515469\nage -0.9515469 1.0000000\n```\n:::\n:::\n\n\n\n## Lowess curve\n\n- Sometimes nice to guide the eye: is the trend straight, or not?\n\n- Idea: *lowess curve*. \"Locally weighted least squares\", not affected\n by outliers, not constrained to be linear.\n\n- Lowess is a *guide*: even if straight line appropriate, may\n wiggle/bend a little. Looking for *serious* problems with linearity.\n\n- Add lowess curve to plot using `geom_smooth`:\n\n## Plot with lowess curve\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/icko-1.pdf)\n:::\n:::\n\n\n\n## The regression\n\nScatterplot shows no obvious curve, and a pretty clear downward trend.\nSo we can run the regression:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 <- lm(atst ~ age, data = sleep)\n```\n:::\n\n\n\n## The output\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = atst ~ age, data = sleep)\n\nResiduals:\n Min 1Q Median 3Q Max \n-23.011 -9.365 2.372 6.770 20.411 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 646.483 12.918 50.05 2.49e-14 ***\nage -14.041 1.368 -10.26 5.70e-07 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 13.15 on 11 degrees of freedom\nMultiple R-squared: 0.9054,\tAdjusted R-squared: 0.8968 \nF-statistic: 105.3 on 1 and 11 DF, p-value: 5.7e-07\n```\n:::\n:::\n\n\n\n## Conclusions\n\n- The relationship appears to be a straight line, with a downward\n trend.\n\n- $F$-tests for model as a whole and $t$-test for slope (same) both\n confirm this (P-value $5.7\\times 10^{-7}=0.00000057$).\n\n- Slope is $-14$, so a 1-year increase in age goes with a 14-minute\n decrease in ATST on average.\n\n- R-squared is correlation squared (when one $x$ anyway), between 0\n and 1 (1 good, 0 bad).\n\n- Here R-squared is 0.9054, pleasantly high.\n\n## Doing things with the regression output\n\n- Output from regression (and eg. $t$-test) is all right to look at,\n but hard to extract and re-use information from.\n\n- Package `broom` extracts info from model output in way that can be\n used in pipe (later):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 646. 12.9 50.0 2.49e-14\n2 age -14.0 1.37 -10.3 5.70e- 7\n```\n:::\n:::\n\n\n\n## also one-line summary of model:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(sleep.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.905 0.897 13.2 105. 0.000000570 1\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n## Broom part 2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsleep.1 %>% augment(sleep)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 8\n atst age .fitted .resid .hat .sigma .cooksd\n \n 1 586 4.4 585. 1.30 0.312 13.8 0.00320\n 2 462. 14 450. 11.8 0.341 13.0 0.319 \n 3 491. 10.1 505. -13.6 0.0887 13.0 0.0568 \n 4 565 6.7 552. 12.6 0.137 13.1 0.0844 \n 5 462 11.5 485. -23.0 0.141 11.3 0.294 \n 6 532. 9.6 512. 20.4 0.0801 12.0 0.114 \n 7 478. 12.4 472. 5.23 0.198 13.7 0.0243 \n 8 515. 8.9 522. -6.32 0.0772 13.6 0.0105 \n 9 493 11.1 491. 2.37 0.122 13.8 0.00258\n10 528. 7.75 538. -9.37 0.0954 13.4 0.0296 \n11 576. 5.5 569. 6.64 0.214 13.6 0.0441 \n12 532. 8.6 526. 6.77 0.0792 13.6 0.0124 \n13 530. 7.2 545. -14.9 0.114 12.9 0.0933 \n# i 1 more variable: .std.resid \n```\n:::\n:::\n\n\n\nUseful for plotting residuals against an $x$-variable.\n\n## CI for mean response and prediction intervals\n\nOnce useful regression exists, use it for prediction:\n\n- To get a single number for prediction at a given $x$, substitute\n into regression equation, eg. age 10: predicted ATST is\n $646.48-14.04(10)=506$ minutes.\n\n- To express uncertainty of this prediction:\n\n- *CI for mean response* expresses uncertainty about mean ATST for all\n children aged 10, based on data.\n\n- *Prediction interval* expresses uncertainty about predicted ATST for\n a new child aged 10 whose ATST not known. More uncertain.\n\n- Also do above for a child aged 5.\n\n## The `marginaleffects` package 1/2\n\nTo get predictions for specific values, set up a dataframe with those\nvalues first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew <- datagrid(model = sleep.1, age = c(10, 5))\nnew\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n atst age\n1 519.3038 10\n2 519.3038 5\n```\n:::\n:::\n\n\n\nAny variables in the dataframe that you don't specify are set to their\nmean values (quantitative) or most common category (categorical).\n\n## The `marginaleffects` package 2/2\n\nThen feed into `newdata` in `predictions`. This contains a lot of\ncolumns, so you probably want only to display the ones you care about:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncbind(predictions(sleep.1, newdata = new)) %>% \n select(estimate, conf.low, conf.high, age)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n estimate conf.low conf.high age\n1 576.2781 563.2588 589.2974 5\n2 506.0729 498.4899 513.6558 10\n```\n:::\n:::\n\n\n\nThe confidence limits are a 95% confidence interval for the mean\nresponse at that `age`.\n\n## Prediction intervals\n\nThese are obtained (instead) with `predict` as below. Use the same\ndataframe `new` as before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npp <- predict(sleep.1, new, interval = \"p\")\npp\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n fit lwr upr\n1 506.0729 475.8982 536.2475\n2 576.2781 543.8474 608.7088\n```\n:::\n\n```{.r .cell-code}\ncbind(new, pp) %>% select(-atst)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n age fit lwr upr\n1 10 506.0729 475.8982 536.2475\n2 5 576.2781 543.8474 608.7088\n```\n:::\n:::\n\n\n\n## Comments\n\n- Age 10 closer to centre of data, so intervals are both narrower than\n those for age 5.\n\n- Prediction intervals bigger than CI for mean (additional\n uncertainty).\n\n- Technical note: output from `predict` is R `matrix`, not data frame,\n so Tidyverse `bind_cols` does not work. Use base R `cbind`.\n\n## That grey envelope\n\nMarks confidence interval for mean for all $x$:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep, aes(x = age, y = atst)) + geom_point() +\n geom_smooth(method = \"lm\") +\n scale_y_continuous(breaks = seq(420, 600, 20))\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-15-1.pdf)\n:::\n:::\n\n\n\n## Diagnostics\n\nHow to tell whether a straight-line regression is appropriate?\n\n- Before: check scatterplot for straight trend.\n\n- After: plot *residuals* (observed minus predicted response) against\n predicted values. Aim: a plot with no pattern.\n\n## Residual plot\n\nNot much pattern here --- regression appropriate.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(sleep.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/akjhkadjfhjahnkkk-1.pdf)\n:::\n:::\n\n\n\n## An inappropriate regression\n\nDifferent data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/curvy.txt\"\ncurvy <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n## Scatterplot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy, aes(x = xx, y = yy)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-16-1.pdf)\n:::\n:::\n\n\n\n## Regression line, anyway\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.1 <- lm(yy ~ xx, data = curvy)\nsummary(curvy.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = yy ~ xx, data = curvy)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.582 -2.204 0.000 1.514 3.509 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 7.5818 1.5616 4.855 0.00126 **\nxx 0.9818 0.2925 3.356 0.00998 **\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.657 on 8 degrees of freedom\nMultiple R-squared: 0.5848,\tAdjusted R-squared: 0.5329 \nF-statistic: 11.27 on 1 and 8 DF, p-value: 0.009984\n```\n:::\n:::\n\n\n\n## Residual plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/altoadige-1.pdf)\n:::\n:::\n\n\n\n## No good: fixing it up\n\n- Residual plot has *curve*: middle residuals positive, high and low\n ones negative. Bad.\n\n- Fitting a curve would be better. Try this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2 <- lm(yy ~ xx + I(xx^2), data = curvy)\n```\n:::\n\n\n\n- Adding `xx`-squared term, to allow for curve.\n\n- Another way to do same thing: specify how model *changes*:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncurvy.2a <- update(curvy.1, . ~ . + I(xx^2))\n```\n:::\n\n\n\n## Regression 2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(curvy.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 3.9 0.773 5.04 0.00149 \n2 xx 3.74 0.400 9.36 0.0000331\n3 I(xx^2) -0.307 0.0428 -7.17 0.000182 \n```\n:::\n\n```{.r .cell-code}\nglance(curvy.2) #\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.950 0.936 0.983 66.8 0.0000275 2\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n## Comments\n\n- `xx`-squared term definitely significant (P-value 0.000182), so need\n this curve to describe relationship.\n\n- Adding squared term has made R-squared go up from 0.5848 to 0.9502:\n great improvement.\n\n- This is a definite curve!\n\n## The residual plot now\n\nNo problems any more:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(curvy.2, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-21-1.pdf)\n:::\n:::\n\n\n\n## Another way to handle curves\n\n- Above, saw that changing $x$ (adding $x^2$) was a way of handling\n curved relationships.\n\n- Another way: change $y$ (transformation).\n\n- Can guess how to change $y$, or might be theory:\n\n- example: relationship $y=ae^{bx}$ (exponential growth):\n\n- take logs to get $\\ln y=\\ln a + bx$.\n\n- Taking logs has made relationship linear ($\\ln y$ as response).\n\n- Or, *estimate* transformation, using Box-Cox method.\n\n## Box-Cox\n\n- Install package `MASS` via `install.packages(\"MASS\")` (only need to\n do *once*)\n\n- Every R session you want to use something in `MASS`, type\n `library(MASS)`\n\n## Some made-up data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/madeup2.csv\"\nmadeup <- read_csv(my_url)\nmadeup\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 3\n ...1 x y\n \n1 1 0 17.9\n2 2 1 33.6\n3 3 2 82.7\n4 4 3 31.2\n5 5 4 177. \n6 6 5 359. \n7 7 6 469. \n8 8 7 283. \n```\n:::\n:::\n\n\n\nSeems to be faster-than-linear growth, maybe exponential growth.\n\n## Scatterplot: faster than linear growth\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = y)) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/dsljhsdjlhf-1.pdf)\n:::\n:::\n\n\n\n## Running Box-Cox\n\n- `library(MASS)` first.\n\n- Feed `boxcox` a model formula with a squiggle in it, such as you\n would use for `lm`.\n\n- Output: a graph (next page):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(y ~ x, data = madeup)\n```\n:::\n\n\n\n## The Box-Cox output\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-beamer/trento-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- $\\lambda$ (lambda) is the power by which you should transform $y$ to\n get the relationship straight (straighter). Power 0 is \"take logs\"\n\n- Middle dotted line marks best single value of $\\lambda$ (here about\n 0.1).\n\n- Outer dotted lines mark 95% CI for $\\lambda$, here $-0.3$ to 0.7,\n approx. (Rather uncertain about best transformation.)\n\n- Any power transformation within the CI supported by data. In this\n case, log ($\\lambda=0$) and square root ($\\lambda=0.5$) good, but no\n transformation ($\\lambda=1$) not.\n\n- Pick a \"round-number\" value of $\\lambda$ like $2,1,0.5,0,-0.5,-1$.\n Here 0 and 0.5 good values to pick.\n\n## Did transformation straighten things?\n\n- Plot transformed $y$ against $x$. Here, log:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(madeup, aes(x = x, y = log(y))) + geom_point() +\n geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-24-1.pdf)\n:::\n:::\n\n\n\nLooks much straighter.\n\n## Regression with transformed $y$\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmadeup.1 <- lm(log(y) ~ x, data = madeup)\nglance(madeup.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.811 0.779 0.588 25.7 0.00228 1\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n\n```{.r .cell-code}\ntidy(madeup.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 3.03 0.379 7.98 0.000206\n2 x 0.460 0.0907 5.07 0.00228 \n```\n:::\n:::\n\n\n\nR-squared now decently high.\n\n## Multiple regression\n\n- What if more than one $x$? Extra issues:\n\n - Now one intercept and a slope for each $x$: how to interpret?\n\n - Which $x$-variables actually help to predict $y$?\n\n - Different interpretations of \"global\" $F$-test and individual\n $t$-tests.\n\n - R-squared no longer correlation squared, but still interpreted\n as \"higher better\".\n\n - In `lm` line, add extra $x$s after `~`.\n\n - Interpretation not so easy (and other problems that can occur).\n\n## Multiple regression example\n\nStudy of women and visits to health professionals, and how the number of\nvisits might be related to other variables:\n\n\n\n```{=tex}\n\\begin{description}\n\\item[timedrs:] number of visits to health professionals (over course of study)\n\\item[phyheal:] number of physical health problems\n\\item[menheal:] number of mental health problems\n\\item[stress:] result of questionnaire about number and type of life changes\n\\end{description}\n```\n\n\n`timedrs` response, others explanatory.\n\n## The data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/regressx.txt\"\nvisits <- read_delim(my_url, \" \")\n```\n:::\n\n\n\n## Check data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 465 x 5\n subjno timedrs phyheal menheal stress\n \n 1 1 1 5 8 265\n 2 2 3 4 6 415\n 3 3 0 3 4 92\n 4 4 13 2 2 241\n 5 5 15 3 6 86\n 6 6 3 5 5 247\n 7 7 2 5 6 13\n 8 8 0 4 5 12\n 9 9 7 5 4 269\n10 10 4 3 9 391\n# i 455 more rows\n```\n:::\n:::\n\n\n\n## Fit multiple regression\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.1 <- lm(timedrs ~ phyheal + menheal + stress,\n data = visits)\nsummary(visits.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-14.792 -4.353 -1.815 0.902 65.886 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -3.704848 1.124195 -3.296 0.001058 ** \nphyheal 1.786948 0.221074 8.083 5.6e-15 ***\nmenheal -0.009666 0.129029 -0.075 0.940318 \nstress 0.013615 0.003612 3.769 0.000185 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 9.708 on 461 degrees of freedom\nMultiple R-squared: 0.2188,\tAdjusted R-squared: 0.2137 \nF-statistic: 43.03 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n## The slopes\n\n- Model as a whole strongly significant even though R-sq not very big\n (lots of data). At least one of the $x$'s predicts `timedrs`.\n\n- The physical health and stress variables definitely help to predict\n the number of visits, but *with those in the model* we don't need\n `menheal`. However, look at prediction of `timedrs` from `menheal`\n by itself:\n\n## Just `menheal`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.2 <- lm(timedrs ~ menheal, data = visits)\nsummary(visits.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = timedrs ~ menheal, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-13.826 -5.150 -2.818 1.177 72.513 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 3.8159 0.8702 4.385 1.44e-05 ***\nmenheal 0.6672 0.1173 5.688 2.28e-08 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 10.6 on 463 degrees of freedom\nMultiple R-squared: 0.06532,\tAdjusted R-squared: 0.0633 \nF-statistic: 32.35 on 1 and 463 DF, p-value: 2.279e-08\n```\n:::\n:::\n\n\n\n## `menheal` by itself\n\n- `menheal` by itself *does* significantly help to predict `timedrs`.\n\n- But the R-sq is much less (6.5% vs. 22%).\n\n- So other two variables do a better job of prediction.\n\n- With those variables in the regression (`phyheal` and `stress`),\n don't need `menheal` *as well*.\n\n## Investigating via correlation\n\nLeave out first column (`subjno`):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits %>% select(-subjno) %>% cor()\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n timedrs phyheal menheal stress\ntimedrs 1.0000000 0.4395293 0.2555703 0.2865951\nphyheal 0.4395293 1.0000000 0.5049464 0.3055517\nmenheal 0.2555703 0.5049464 1.0000000 0.3697911\nstress 0.2865951 0.3055517 0.3697911 1.0000000\n```\n:::\n:::\n\n\n\n- `phyheal` most strongly correlated with `timedrs`.\n\n- Not much to choose between other two.\n\n- But `menheal` has higher correlation with `phyheal`, so not as much\n to *add* to prediction as `stress`.\n\n- Goes to show things more complicated in multiple regression.\n\n## Residual plot (from `timedrs` on all)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = .resid)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/iffy8-1.pdf)\n:::\n:::\n\n\n\nApparently random. But...\n\n## Normal quantile plot of residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-32-1.pdf)\n:::\n:::\n\n\n\nNot normal at all; upper tail is way too long.\n\n## Absolute residuals\n\nIs there trend in *size* of residuals (fan-out)? Plot *absolute value*\nof residual against fitted value:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.1, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-33-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- On the normal quantile plot:\n\n - highest (most positive) residuals are *way* too high\n\n - distribution of residuals skewed to right (not normal at all)\n\n- On plot of absolute residuals:\n\n - size of residuals getting bigger as fitted values increase\n\n - predictions getting more variable as fitted values increase\n\n - that is, predictions getting *less accurate* as fitted values\n increase, but predictions should be equally accurate all way\n along.\n\n- Both indicate problems with regression, of kind that transformation\n of response often fixes: that is, predict *function* of response\n `timedrs` instead of `timedrs` itself.\n\n## Box-Cox transformations\n\n- Taking log of `timedrs` and having it work: lucky guess. How to find\n good transformation?\n\n- Box-Cox again.\n\n- Extra problem: some of `timedrs` values are 0, but Box-Cox expects\n all +. Note response for `boxcox`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress, data = visits)\n```\n:::\n\n\n\n## Try 1\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-36-1.pdf)\n:::\n:::\n\n\n\n## Comments on try 1\n\n- Best: $\\lambda$ just less than zero.\n\n- Hard to see scale.\n\n- Focus on $\\lambda$ in $(-0.3,0.1)$:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy.lambda <- seq(-0.3, 0.1, 0.01)\nmy.lambda\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] -0.30 -0.29 -0.28 -0.27 -0.26 -0.25 -0.24 -0.23 -0.22\n[10] -0.21 -0.20 -0.19 -0.18 -0.17 -0.16 -0.15 -0.14 -0.13\n[19] -0.12 -0.11 -0.10 -0.09 -0.08 -0.07 -0.06 -0.05 -0.04\n[28] -0.03 -0.02 -0.01 0.00 0.01 0.02 0.03 0.04 0.05\n[37] 0.06 0.07 0.08 0.09 0.10\n```\n:::\n:::\n\n\n\n## Try 2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox(timedrs + 1 ~ phyheal + menheal + stress,\n lambda = my.lambda,\n data = visits\n)\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-38-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Best: $\\lambda$ just about $-0.07$.\n\n- CI for $\\lambda$ about $(-0.14,0.01)$.\n\n- Only nearby round number: $\\lambda=0$, log transformation.\n\n## Fixing the problems\n\n- Try regression again, with transformed response instead of original\n one.\n\n- Then check residual plot to see that it is OK now.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.3 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress,\n data = visits\n)\n```\n:::\n\n\n\n- `timedrs+1` because some `timedrs` values 0, can't take log of 0.\n\n- Won't usually need to worry about this, but when response could be\n zero/negative, fix that before transformation.\n\n## Output\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(visits.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = log(timedrs + 1) ~ phyheal + menheal + stress, data = visits)\n\nResiduals:\n Min 1Q Median 3Q Max \n-1.95865 -0.44076 -0.02331 0.42304 2.36797 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.3903862 0.0882908 4.422 1.22e-05 ***\nphyheal 0.2019361 0.0173624 11.631 < 2e-16 ***\nmenheal 0.0071442 0.0101335 0.705 0.481 \nstress 0.0013158 0.0002837 4.638 4.58e-06 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.7625 on 461 degrees of freedom\nMultiple R-squared: 0.3682,\tAdjusted R-squared: 0.3641 \nF-statistic: 89.56 on 3 and 461 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n## Comments\n\n- Model as a whole strongly significant again\n\n- R-sq higher than before (37% vs. 22%) suggesting things more linear\n now\n\n- Same conclusion re `menheal`: can take out of regression.\n\n- Should look at residual plots (next pages). Have we fixed problems?\n\n## Residuals against fitted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-41-1.pdf)\n:::\n:::\n\n\n\n## Normal quantile plot of residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-42-1.pdf)\n:::\n:::\n\n\n\n## Absolute residuals against fitted\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(visits.3, aes(x = .fitted, y = abs(.resid))) +\n geom_point() + geom_smooth()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/bRegression-43-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Residuals vs. fitted looks a lot more random.\n\n- Normal quantile plot looks a lot more normal (though still a little\n right-skewness)\n\n- Absolute residuals: not so much trend (though still some).\n\n- Not perfect, but much improved.\n\n## Testing more than one $x$ at once\n\n- The $t$-tests test only whether one variable could be taken out of\n the regression you're looking at.\n- To test significance of more than one variable at once, fit model\n with and without variables\n - then use `anova` to compare fit of models:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvisits.5 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress, \n data = visits)\nvisits.6 <- lm(log(timedrs + 1) ~ stress, data = visits)\n```\n:::\n\n\n\n## Results of tests\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(visits.6, visits.5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nModel 1: log(timedrs + 1) ~ stress\nModel 2: log(timedrs + 1) ~ phyheal + menheal + stress\n Res.Df RSS Df Sum of Sq F Pr(>F) \n1 463 371.47 \n2 461 268.01 2 103.46 88.984 < 2.2e-16 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n- Models don't fit equally well, so bigger one fits better.\n\n- Or \"taking both variables out makes the fit worse, so don't do it\".\n\n- Taking out those $x$'s is a mistake. Or putting them in is a good\n idea.\n\n## The punting data\n\nData set `punting.txt` contains 4 variables for 13 right-footed football\nkickers (punters): left leg and right leg strength (lbs), distance\npunted (ft), another variable called \"fred\". Predict punting distance\nfrom other variables:\n\n\\scriptsize\n\n``` \nleft right punt fred\n170 170 162.50 171 \n130 140 144.0 136 \n170 180 174.50 174 \n160 160 163.50 161 \n150 170 192.0 159 \n150 150 171.75 151 \n180 170 162.0 174 \n110 110 104.83 111 \n110 120 105.67 114 \n120 130 117.58 126 \n140 120 140.25 129 \n130 140 150.17 136 \n150 160 165.17 154 \n```\n\n## Reading in\n\n- Separated by *multiple spaces* with *columns lined up*:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/punting.txt\"\npunting <- read_table(my_url)\n```\n:::\n\n\n\n## The data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 4\n left right punt fred\n \n 1 170 170 162. 171\n 2 130 140 144 136\n 3 170 180 174. 174\n 4 160 160 164. 161\n 5 150 170 192 159\n 6 150 150 172. 151\n 7 180 170 162 174\n 8 110 110 105. 111\n 9 110 120 106. 114\n10 120 130 118. 126\n11 140 120 140. 129\n12 130 140 150. 136\n13 150 160 165. 154\n```\n:::\n:::\n\n\n\n## Regression and output\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.1 <- lm(punt ~ left + right + fred, data = punting)\nglance(punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df\n \n1 0.778 0.704 14.7 10.5 0.00267 3\n# i 6 more variables: logLik , AIC , BIC ,\n# deviance , df.residual , nobs \n```\n:::\n\n```{.r .cell-code}\ntidy(punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -4.69 29.1 -0.161 0.876\n2 left 0.268 2.11 0.127 0.902\n3 right 1.05 2.15 0.490 0.636\n4 fred -0.267 4.23 -0.0632 0.951\n```\n:::\n\n```{.r .cell-code}\nsummary(punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left + right + fred, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-14.9325 -11.5618 -0.0315 9.0415 20.0886 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|)\n(Intercept) -4.6855 29.1172 -0.161 0.876\nleft 0.2679 2.1111 0.127 0.902\nright 1.0524 2.1477 0.490 0.636\nfred -0.2672 4.2266 -0.063 0.951\n\nResidual standard error: 14.68 on 9 degrees of freedom\nMultiple R-squared: 0.7781,\tAdjusted R-squared: 0.7042 \nF-statistic: 10.52 on 3 and 9 DF, p-value: 0.00267\n```\n:::\n:::\n\n\n\n## Comments\n\n- Overall regression strongly significant, R-sq high.\n\n- None of the $x$'s significant! Why?\n\n- $t$-tests only say that you could take any one of the $x$'s out\n without damaging the fit; doesn't matter which one.\n\n- Explanation: look at *correlations*.\n\n## The correlations\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncor(punting)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n left right punt fred\nleft 1.0000000 0.8957224 0.8117368 0.9722632\nright 0.8957224 1.0000000 0.8805469 0.9728784\npunt 0.8117368 0.8805469 1.0000000 0.8679507\nfred 0.9722632 0.9728784 0.8679507 1.0000000\n```\n:::\n:::\n\n\n\n- *All* correlations are high: $x$'s with `punt` (good) and with each\n other (bad, at least confusing).\n\n- What to do? Probably do just as well to pick one variable, say\n `right` since kickers are right-footed.\n\n## Just `right`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 <- lm(punt ~ right, data = punting)\nsummary(punting.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ right, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-15.7576 -11.0611 0.3656 7.8890 19.0423 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -3.6930 25.2649 -0.146 0.886 \nright 1.0427 0.1692 6.162 7.09e-05 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 13.36 on 11 degrees of freedom\nMultiple R-squared: 0.7754,\tAdjusted R-squared: 0.7549 \nF-statistic: 37.97 on 1 and 11 DF, p-value: 7.088e-05\n```\n:::\n\n```{.r .cell-code}\nanova(punting.2, punting.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nModel 1: punt ~ right\nModel 2: punt ~ left + right + fred\n Res.Df RSS Df Sum of Sq F Pr(>F)\n1 11 1962.5 \n2 9 1938.2 2 24.263 0.0563 0.9456\n```\n:::\n\n```{.r .cell-code}\npunting.3 <- lm(punt ~ left, data = punting)\nsummary(punting.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-22.840 -12.298 -2.234 8.990 35.820 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 12.8834 30.1575 0.427 0.677474 \nleft 0.9553 0.2072 4.610 0.000753 ***\n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 16.46 on 11 degrees of freedom\nMultiple R-squared: 0.6589,\tAdjusted R-squared: 0.6279 \nF-statistic: 21.25 on 1 and 11 DF, p-value: 0.0007528\n```\n:::\n:::\n\n\n\nNo significant loss by dropping other two variables.\n\n## Comparing R-squareds\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.1)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7781401\n```\n:::\n\n```{.r .cell-code}\nsummary(punting.2)$r.squared\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 0.7753629\n```\n:::\n:::\n\n\n\nBasically no difference. In regression (over), `right` significant:\n\n## Regression results\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(punting.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -3.69 25.3 -0.146 0.886 \n2 right 1.04 0.169 6.16 0.0000709\n```\n:::\n:::\n\n\n\n## But\\ldots\n\n- Maybe we got the *form* of the relationship with `left` wrong.\n\n- Check: plot *residuals* from previous regression (without `left`)\n against `left`.\n\n- Residuals here are \"punting distance adjusted for right leg\n strength\".\n\n- If there is some kind of relationship with `left`, we should include\n in model.\n\n- Plot of residuals against original variable: `augment` from `broom`.\n\n## Augmenting `punting.2`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.2 %>% augment(punting) -> punting.2.aug\npunting.2.aug \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 13 x 10\n left right punt fred .fitted .resid .hat .sigma\n \n 1 170 170 162. 171 174. -11.1 0.157 13.5\n 2 130 140 144 136 142. 1.72 0.0864 14.0\n 3 170 180 174. 174 184. -9.49 0.244 13.6\n 4 160 160 164. 161 163. 0.366 0.101 14.0\n 5 150 170 192 159 174. 18.4 0.157 12.5\n 6 150 150 172. 151 153. 19.0 0.0778 12.5\n 7 180 170 162 174 174. -11.6 0.157 13.4\n 8 110 110 105. 111 111. -6.17 0.305 13.8\n 9 110 120 106. 114 121. -15.8 0.2 12.9\n10 120 130 118. 126 132. -14.3 0.127 13.1\n11 140 120 140. 129 121. 18.8 0.2 12.3\n12 130 140 150. 136 142. 7.89 0.0864 13.8\n13 150 160 165. 154 163. 2.04 0.101 14.0\n# i 2 more variables: .cooksd , .std.resid \n```\n:::\n:::\n\n\n\n## Residuals against `left`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(punting.2.aug, aes(x = left, y = .resid)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](regression_files/figure-beamer/basingstoke-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- There is a *curved* relationship with `left`.\n\n- We should add `left`-squared to the regression (and therefore put\n `left` back in when we do that):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npunting.3 <- lm(punt ~ left + I(left^2) + right,\n data = punting\n)\n```\n:::\n\n\n\n## Regression with `left-squared`\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(punting.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = punt ~ left + I(left^2) + right, data = punting)\n\nResiduals:\n Min 1Q Median 3Q Max \n-11.3777 -5.3599 0.0459 4.5088 13.2669 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -4.623e+02 9.902e+01 -4.669 0.00117 **\nleft 6.888e+00 1.462e+00 4.710 0.00110 **\nI(left^2) -2.302e-02 4.927e-03 -4.672 0.00117 **\nright 7.396e-01 2.292e-01 3.227 0.01038 * \n---\nSignif. codes: \n0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 7.931 on 9 degrees of freedom\nMultiple R-squared: 0.9352,\tAdjusted R-squared: 0.9136 \nF-statistic: 43.3 on 3 and 9 DF, p-value: 1.13e-05\n```\n:::\n:::\n\n\n\n## Comments\n\n- This was definitely a good idea (R-squared has clearly increased).\n\n- We would never have seen it without plotting residuals from\n `punting.2` (without `left`) against `left`.\n\n- Negative slope for `leftsq` means that increased left-leg strength\n only increases punting distance up to a point: beyond that, it\n decreases again.\n", "supporting": [ "regression_files/figure-beamer" ], diff --git a/_freeze/regression/figure-beamer/akjhkadjfhjahnkkk-1.pdf b/_freeze/regression/figure-beamer/akjhkadjfhjahnkkk-1.pdf index 13e71c2..dffce8a 100644 Binary files a/_freeze/regression/figure-beamer/akjhkadjfhjahnkkk-1.pdf and b/_freeze/regression/figure-beamer/akjhkadjfhjahnkkk-1.pdf differ diff --git a/_freeze/regression/figure-beamer/altoadige-1.pdf b/_freeze/regression/figure-beamer/altoadige-1.pdf index 1b26eae..2479775 100644 Binary files a/_freeze/regression/figure-beamer/altoadige-1.pdf and b/_freeze/regression/figure-beamer/altoadige-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-15-1.pdf b/_freeze/regression/figure-beamer/bRegression-15-1.pdf index bd5a93b..44c871e 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-15-1.pdf and b/_freeze/regression/figure-beamer/bRegression-15-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-16-1.pdf b/_freeze/regression/figure-beamer/bRegression-16-1.pdf index e601bc8..11bfd13 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-16-1.pdf and b/_freeze/regression/figure-beamer/bRegression-16-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-21-1.pdf b/_freeze/regression/figure-beamer/bRegression-21-1.pdf index 25e0a55..4226cf8 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-21-1.pdf and b/_freeze/regression/figure-beamer/bRegression-21-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-24-1.pdf b/_freeze/regression/figure-beamer/bRegression-24-1.pdf index d562bc4..f20551f 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-24-1.pdf and b/_freeze/regression/figure-beamer/bRegression-24-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-32-1.pdf b/_freeze/regression/figure-beamer/bRegression-32-1.pdf index a79da4d..675d70c 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-32-1.pdf and b/_freeze/regression/figure-beamer/bRegression-32-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-33-1.pdf b/_freeze/regression/figure-beamer/bRegression-33-1.pdf index 46e51a2..d4497db 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-33-1.pdf and b/_freeze/regression/figure-beamer/bRegression-33-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-36-1.pdf b/_freeze/regression/figure-beamer/bRegression-36-1.pdf index 12c9d74..758d705 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-36-1.pdf and b/_freeze/regression/figure-beamer/bRegression-36-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-38-1.pdf b/_freeze/regression/figure-beamer/bRegression-38-1.pdf index 0e4091e..5c21c32 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-38-1.pdf and b/_freeze/regression/figure-beamer/bRegression-38-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-41-1.pdf b/_freeze/regression/figure-beamer/bRegression-41-1.pdf index 7e7c96a..f6612c6 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-41-1.pdf and b/_freeze/regression/figure-beamer/bRegression-41-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-42-1.pdf b/_freeze/regression/figure-beamer/bRegression-42-1.pdf index cb561a8..14efaae 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-42-1.pdf and b/_freeze/regression/figure-beamer/bRegression-42-1.pdf differ diff --git a/_freeze/regression/figure-beamer/bRegression-43-1.pdf b/_freeze/regression/figure-beamer/bRegression-43-1.pdf index 190c0e2..89c2bd9 100644 Binary files a/_freeze/regression/figure-beamer/bRegression-43-1.pdf and b/_freeze/regression/figure-beamer/bRegression-43-1.pdf differ diff --git a/_freeze/regression/figure-beamer/basingstoke-1.pdf b/_freeze/regression/figure-beamer/basingstoke-1.pdf index 1f97278..a80169f 100644 Binary files a/_freeze/regression/figure-beamer/basingstoke-1.pdf and b/_freeze/regression/figure-beamer/basingstoke-1.pdf differ diff --git a/_freeze/regression/figure-beamer/dsljhsdjlhf-1.pdf b/_freeze/regression/figure-beamer/dsljhsdjlhf-1.pdf index dc7bc2c..4e2e501 100644 Binary files a/_freeze/regression/figure-beamer/dsljhsdjlhf-1.pdf and b/_freeze/regression/figure-beamer/dsljhsdjlhf-1.pdf differ diff --git a/_freeze/regression/figure-beamer/icko-1.pdf b/_freeze/regression/figure-beamer/icko-1.pdf index 7a5a97e..af1d310 100644 Binary files a/_freeze/regression/figure-beamer/icko-1.pdf and b/_freeze/regression/figure-beamer/icko-1.pdf differ diff --git a/_freeze/regression/figure-beamer/iffy8-1.pdf b/_freeze/regression/figure-beamer/iffy8-1.pdf index eccdce6..0203f82 100644 Binary files a/_freeze/regression/figure-beamer/iffy8-1.pdf and b/_freeze/regression/figure-beamer/iffy8-1.pdf differ diff --git a/_freeze/regression/figure-beamer/suggo-1.pdf b/_freeze/regression/figure-beamer/suggo-1.pdf index 4716962..709dc85 100644 Binary files a/_freeze/regression/figure-beamer/suggo-1.pdf and b/_freeze/regression/figure-beamer/suggo-1.pdf differ diff --git a/_freeze/regression/figure-beamer/trento-1.pdf b/_freeze/regression/figure-beamer/trento-1.pdf index def4f07..df4e8a3 100644 Binary files a/_freeze/regression/figure-beamer/trento-1.pdf and b/_freeze/regression/figure-beamer/trento-1.pdf differ diff --git a/_freeze/regression/figure-revealjs/bRegression-15-1.png b/_freeze/regression/figure-revealjs/bRegression-15-1.png index f9ac6b0..af2de50 100644 Binary files a/_freeze/regression/figure-revealjs/bRegression-15-1.png and b/_freeze/regression/figure-revealjs/bRegression-15-1.png differ diff --git a/_freeze/regression/figure-revealjs/icko-1.png b/_freeze/regression/figure-revealjs/icko-1.png index 1dfd3b5..3c95e53 100644 Binary files a/_freeze/regression/figure-revealjs/icko-1.png and b/_freeze/regression/figure-revealjs/icko-1.png differ diff --git a/_freeze/regression/figure-revealjs/suggo-1.png b/_freeze/regression/figure-revealjs/suggo-1.png index 7f8121d..083c322 100644 Binary files a/_freeze/regression/figure-revealjs/suggo-1.png and b/_freeze/regression/figure-revealjs/suggo-1.png differ diff --git a/_freeze/tidy_extra/execute-results/html.json b/_freeze/tidy_extra/execute-results/html.json index 9695097..489f613 100644 --- a/_freeze/tidy_extra/execute-results/html.json +++ b/_freeze/tidy_extra/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "bd44db78bc2153519cc1de9a1838ed30", + "hash": "c73869223a8d422d0d3d06ad626d2610", "result": { - "markdown": "---\ntitle: \"Tidying data: extras\"\n---\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## The pig feed data again\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/STAC32/pigs1.txt\"\npigs <- read_table(my_url)\npigs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Make longer (as before)\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>% pivot_longer(-pig, names_to=\"feed\", \n values_to=\"weight\") -> pigs_longer\npigs_longer\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Make wider two ways 1/2\n\n`pivot_wider` is inverse of `pivot_longer`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=feed, values_from=weight)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nwe are back where we started.\n\n## Make wider 2/2\n\nOr\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=pig, values_from=weight)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Disease presence and absence at two locations\n\nFrequencies of plants observed with and without disease at two locations:\n\n Species Disease present Disease absent\n Location X Location Y Location X Location Y\n A 44 12 38 10\n B 28 22 20 18\n\nThis has two rows of headers, so I rewrote the data file:\n\n Species present_x present_y absent_x absent_y\n A 44 12 38 10\n B 28 22 20 18\n\nRead into data frame called `prevalence`.\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Lengthen and separate\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to = \"column\", \n values_to = \"freq\") %>% \n separate_wider_delim(column, \"_\", \n names = c(\"disease\", \"location\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making longer, the better way\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to=c(\"disease\", \"location\"),\n names_sep=\"_\", values_to=\"frequency\") %>% \n arrange(Species, location, disease) -> prevalence_longer\nprevalence_longer\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making wider, different ways\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=c(Species, location), values_from=frequency)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=location, values_from=frequency)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Interlude {.smaller}\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(weight_mean=mean(weight))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## What if summary is more than one number?\n\neg. quartiles:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=quantile(weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## this also works\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(quantile(weight, c(0.25, 0.75)))) %>% \n unnest(r)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n## or, even better, use `enframe`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nquantile(pigs_longer$weight, c(0.25, 0.75))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n 25% 75% \n65.975 90.225 \n```\n:::\n\n```{.r .cell-code}\nenframe(quantile(pigs_longer$weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## A nice look\n\nRun this one line at a time to see how it works: \n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(enframe(quantile(weight, c(0.25, 0.75))))) %>% \n unnest(r) %>% \n pivot_wider(names_from=name, values_from=value) \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## A hairy one\n\n18 people receive one of three treatments. At 3 different times (pre, post, followup) two variables `y` and `z` are measured on each person:\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Attempt 1\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \"var\"),\n names_sep=\"_\"\n )\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThis is *too* long! We wanted a column called `y` and a column called `z`, but they have been pivoted-longer too. \n\n## Attempt 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \".value\"),\n names_sep=\"_\"\n ) -> repmes3\nrepmes3\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThis has done what we wanted.\n\n## make a graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(repmes3, aes(x=fct_inorder(time), y=y, \n colour=treatment, group=id)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-revealjs/tidy-extra-R-19-1.png){width=960}\n:::\n:::\n\n\nA so-called spaghetti plot. The three measurements for each person are joined by lines, and the lines are coloured by treatment.\n\n## or do the plot with means\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes3 %>% group_by(treatment, ftime=fct_inorder(time)) %>% \n summarize(mean_y=mean(y)) %>% \n ggplot(aes(x=ftime, y=mean_y, colour=treatment, \n group=treatment)) + \n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-revealjs/tidy-extra-R-20-1.png){width=960}\n:::\n:::\n\n\nOn average, the two real treatments go up and level off, but the control group is very different.\n\n", + "markdown": "---\ntitle: \"Tidying data: extras\"\n---\n\n\n## Packages\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## The pig feed data again\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs1.txt\"\npigs <- read_table(my_url)\npigs\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Make longer (as before)\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>% pivot_longer(-pig, names_to=\"feed\", \n values_to=\"weight\") -> pigs_longer\npigs_longer\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Make wider two ways 1/2\n\n`pivot_wider` is inverse of `pivot_longer`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=feed, values_from=weight)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nwe are back where we started.\n\n## Make wider 2/2\n\nOr\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=pig, values_from=weight)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Disease presence and absence at two locations\n\nFrequencies of plants observed with and without disease at two\nlocations:\n\n``` \nSpecies Disease present Disease absent\n Location X Location Y Location X Location Y\nA 44 12 38 10\nB 28 22 20 18\n```\n\nThis has two rows of headers, so I rewrote the data file:\n\n``` \nSpecies present_x present_y absent_x absent_y\nA 44 12 38 10\nB 28 22 20 18\n```\n\nRead into data frame called `prevalence`.\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Lengthen and separate\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to = \"column\", \n values_to = \"freq\") %>% \n separate_wider_delim(column, \"_\", \n names = c(\"disease\", \"location\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making longer, the better way\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to=c(\"disease\", \"location\"),\n names_sep=\"_\", \n values_to=\"frequency\") -> prevalence_longer \nprevalence_longer\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making wider, different ways\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=c(Species, location), values_from=frequency)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=location, values_from=frequency)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Interlude {.smaller}\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(weight_mean=mean(weight))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## What if summary is more than one number?\n\neg. quartiles:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=quantile(weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## this also works\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(quantile(weight, c(0.25, 0.75)))) %>% \n unnest(r)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## or, even better, use `enframe`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nquantile(pigs_longer$weight, c(0.25, 0.75))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n 25% 75% \n65.975 90.225 \n```\n:::\n\n```{.r .cell-code}\nenframe(quantile(pigs_longer$weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## A nice look\n\nRun this one line at a time to see how it works:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(enframe(quantile(weight, c(0.25, 0.75))))) %>% \n unnest(r) %>% \n pivot_wider(names_from=name, values_from=value) \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## A hairy one\n\n18 people receive one of three treatments. At 3 different times (pre,\npost, followup) two variables `y` and `z` are measured on each person:\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Attempt 1\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \"var\"),\n names_sep=\"_\",\n values_to = \"vvv\"\n )\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThis is *too* long! We wanted a column called `y` and a column called\n`z`, but they have been pivoted-longer too.\n\n## Attempt 2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \".value\"),\n names_sep=\"_\"\n ) -> repmes3\nrepmes3\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nThis has done what we wanted.\n\n## make a graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(repmes3, aes(x=fct_inorder(time), y=y, \n colour=treatment, group=id)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-revealjs/tidy-extra-R-19-1.png){width=960}\n:::\n:::\n\n\nA so-called spaghetti plot. The three measurements for each person are\njoined by lines, and the lines are coloured by treatment.\n\n## or do the plot with means\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes3 %>% group_by(treatment, ftime=fct_inorder(time)) %>% \n summarize(mean_y=mean(y)) %>% \n ggplot(aes(x=ftime, y=mean_y, colour=treatment, \n group=treatment)) + \n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-revealjs/tidy-extra-R-20-1.png){width=960}\n:::\n:::\n\n\nOn average, the two real treatments go up and level off, but the control\ngroup is very different.\n", "supporting": [ "tidy_extra_files/figure-revealjs" ], diff --git a/_freeze/tidy_extra/execute-results/tex.json b/_freeze/tidy_extra/execute-results/tex.json index 889ecee..7f5768e 100644 --- a/_freeze/tidy_extra/execute-results/tex.json +++ b/_freeze/tidy_extra/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "bd44db78bc2153519cc1de9a1838ed30", + "hash": "c73869223a8d422d0d3d06ad626d2610", "result": { - "markdown": "---\ntitle: \"Tidying data: extras\"\n---\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## The pig feed data again\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/STAC32/pigs1.txt\"\npigs <- read_table(my_url)\npigs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n pig feed1 feed2 feed3 feed4\n \n1 1 60.8 68.7 92.6 87.9\n2 2 57 67.7 92.1 84.2\n3 3 65 74 90.2 83.1\n4 4 58.6 66.3 96.5 85.7\n5 5 61.7 69.8 99.1 90.3\n```\n:::\n:::\n\n\n\n## Make longer (as before)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>% pivot_longer(-pig, names_to=\"feed\", \n values_to=\"weight\") -> pigs_longer\npigs_longer\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Make wider two ways 1/2\n\n`pivot_wider` is inverse of `pivot_longer`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=feed, values_from=weight)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n pig feed1 feed2 feed3 feed4\n \n1 1 60.8 68.7 92.6 87.9\n2 2 57 67.7 92.1 84.2\n3 3 65 74 90.2 83.1\n4 4 58.6 66.3 96.5 85.7\n5 5 61.7 69.8 99.1 90.3\n```\n:::\n:::\n\n\n\nwe are back where we started.\n\n## Make wider 2/2\n\nOr\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=pig, values_from=weight)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 6\n feed `1` `2` `3` `4` `5`\n \n1 feed1 60.8 57 65 58.6 61.7\n2 feed2 68.7 67.7 74 66.3 69.8\n3 feed3 92.6 92.1 90.2 96.5 99.1\n4 feed4 87.9 84.2 83.1 85.7 90.3\n```\n:::\n:::\n\n\n\n## Disease presence and absence at two locations\n\nFrequencies of plants observed with and without disease at two locations:\n\n Species Disease present Disease absent\n Location X Location Y Location X Location Y\n A 44 12 38 10\n B 28 22 20 18\n\nThis has two rows of headers, so I rewrote the data file:\n\n Species present_x present_y absent_x absent_y\n A 44 12 38 10\n B 28 22 20 18\n\nRead into data frame called `prevalence`.\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n Species present_x present_y absent_x absent_y\n \n1 A 44 12 38 10\n2 B 28 22 20 18\n```\n:::\n:::\n\n\n\n## Lengthen and separate\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to = \"column\", \n values_to = \"freq\") %>% \n separate_wider_delim(column, \"_\", \n names = c(\"disease\", \"location\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Species disease location freq\n \n1 A present x 44\n2 A present y 12\n3 A absent x 38\n4 A absent y 10\n5 B present x 28\n6 B present y 22\n7 B absent x 20\n8 B absent y 18\n```\n:::\n:::\n\n\n\n## Making longer, the better way\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to=c(\"disease\", \"location\"),\n names_sep=\"_\", values_to=\"frequency\") %>% \n arrange(Species, location, disease) -> prevalence_longer\nprevalence_longer\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Species disease location frequency\n \n1 A absent x 38\n2 A present x 44\n3 A absent y 10\n4 A present y 12\n5 B absent x 20\n6 B present x 28\n7 B absent y 18\n8 B present y 22\n```\n:::\n:::\n\n\n\n## Making wider, different ways\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=c(Species, location), values_from=frequency)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n disease A_x A_y B_x B_y\n \n1 absent 38 10 20 18\n2 present 44 12 28 22\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=location, values_from=frequency)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 4\n Species disease x y\n \n1 A absent 38 10\n2 A present 44 12\n3 B absent 20 18\n4 B present 28 22\n```\n:::\n:::\n\n\n\n## Interlude {.smaller}\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(weight_mean=mean(weight))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n feed weight_mean\n \n1 feed1 60.6\n2 feed2 69.3\n3 feed3 94.1\n4 feed4 86.2\n```\n:::\n:::\n\n\n\n## What if summary is more than one number?\n\neg. quartiles:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=quantile(weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n# Groups: feed [4]\n feed r\n \n1 feed1 58.6\n2 feed1 61.7\n3 feed2 67.7\n4 feed2 69.8\n5 feed3 92.1\n6 feed3 96.5\n7 feed4 84.2\n8 feed4 87.9\n```\n:::\n:::\n\n\n\n## this also works\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(quantile(weight, c(0.25, 0.75)))) %>% \n unnest(r)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n feed r\n \n1 feed1 58.6\n2 feed1 61.7\n3 feed2 67.7\n4 feed2 69.8\n5 feed3 92.1\n6 feed3 96.5\n7 feed4 84.2\n8 feed4 87.9\n```\n:::\n:::\n\n\n\n\n\n## or, even better, use `enframe`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nquantile(pigs_longer$weight, c(0.25, 0.75))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n 25% 75% \n65.975 90.225 \n```\n:::\n\n```{.r .cell-code}\nenframe(quantile(pigs_longer$weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n name value\n \n1 25% 66.0\n2 75% 90.2\n```\n:::\n:::\n\n\n\n## A nice look\n\nRun this one line at a time to see how it works: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(enframe(quantile(weight, c(0.25, 0.75))))) %>% \n unnest(r) %>% \n pivot_wider(names_from=name, values_from=value) \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 3\n feed `25%` `75%`\n \n1 feed1 58.6 61.7\n2 feed2 67.7 69.8\n3 feed3 92.1 96.5\n4 feed4 84.2 87.9\n```\n:::\n:::\n\n\n\n## A hairy one\n\n18 people receive one of three treatments. At 3 different times (pre, post, followup) two variables `y` and `z` are measured on each person:\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 18 x 8\n id treatment pre_y post_y fu_y pre_z post_z fu_z\n \n 1 A.1 A 3 13 9 0 0 9\n 2 A.2 A 0 14 10 6 6 3\n 3 A.3 A 4 6 17 8 2 6\n 4 A.4 A 7 7 13 7 6 4\n 5 A.5 A 3 12 11 6 12 6\n 6 A.6 A 10 14 8 13 3 8\n 7 B.1 B 9 11 17 8 11 27\n 8 B.2 B 4 16 13 9 3 26\n 9 B.3 B 8 10 9 12 0 18\n10 B.4 B 5 9 13 3 0 14\n11 B.5 B 0 15 11 3 0 25\n12 B.6 B 4 11 14 4 2 9\n13 Control.1 Control 10 12 15 4 3 7\n14 Control.2 Control 2 8 12 8 7 20\n15 Control.3 Control 4 9 10 2 0 10\n16 Control.4 Control 10 8 8 5 8 14\n17 Control.5 Control 11 11 11 1 0 11\n18 Control.6 Control 1 5 15 8 9 10\n```\n:::\n:::\n\n\n\n## Attempt 1\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \"var\"),\n names_sep=\"_\"\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 108 x 5\n id treatment time var value\n \n 1 A.1 A pre y 3\n 2 A.1 A post y 13\n 3 A.1 A fu y 9\n 4 A.1 A pre z 0\n 5 A.1 A post z 0\n 6 A.1 A fu z 9\n 7 A.2 A pre y 0\n 8 A.2 A post y 14\n 9 A.2 A fu y 10\n10 A.2 A pre z 6\n# i 98 more rows\n```\n:::\n:::\n\n\n\nThis is *too* long! We wanted a column called `y` and a column called `z`, but they have been pivoted-longer too. \n\n## Attempt 2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \".value\"),\n names_sep=\"_\"\n ) -> repmes3\nrepmes3\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 54 x 5\n id treatment time y z\n \n 1 A.1 A pre 3 0\n 2 A.1 A post 13 0\n 3 A.1 A fu 9 9\n 4 A.2 A pre 0 6\n 5 A.2 A post 14 6\n 6 A.2 A fu 10 3\n 7 A.3 A pre 4 8\n 8 A.3 A post 6 2\n 9 A.3 A fu 17 6\n10 A.4 A pre 7 7\n# i 44 more rows\n```\n:::\n:::\n\n\n\nThis has done what we wanted.\n\n## make a graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(repmes3, aes(x=fct_inorder(time), y=y, \n colour=treatment, group=id)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-beamer/tidy-extra-R-19-1.pdf)\n:::\n:::\n\n\n\nA so-called spaghetti plot. The three measurements for each person are joined by lines, and the lines are coloured by treatment.\n\n## or do the plot with means\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes3 %>% group_by(treatment, ftime=fct_inorder(time)) %>% \n summarize(mean_y=mean(y)) %>% \n ggplot(aes(x=ftime, y=mean_y, colour=treatment, \n group=treatment)) + \n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-beamer/tidy-extra-R-20-1.pdf)\n:::\n:::\n\n\n\nOn average, the two real treatments go up and level off, but the control group is very different.\n\n", + "markdown": "---\ntitle: \"Tidying data: extras\"\n---\n\n\n\n## Packages\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## The pig feed data again\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs1.txt\"\npigs <- read_table(my_url)\npigs\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n pig feed1 feed2 feed3 feed4\n \n1 1 60.8 68.7 92.6 87.9\n2 2 57 67.7 92.1 84.2\n3 3 65 74 90.2 83.1\n4 4 58.6 66.3 96.5 85.7\n5 5 61.7 69.8 99.1 90.3\n```\n:::\n:::\n\n\n\n## Make longer (as before)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>% pivot_longer(-pig, names_to=\"feed\", \n values_to=\"weight\") -> pigs_longer\npigs_longer\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Make wider two ways 1/2\n\n`pivot_wider` is inverse of `pivot_longer`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=feed, values_from=weight)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n pig feed1 feed2 feed3 feed4\n \n1 1 60.8 68.7 92.6 87.9\n2 2 57 67.7 92.1 84.2\n3 3 65 74 90.2 83.1\n4 4 58.6 66.3 96.5 85.7\n5 5 61.7 69.8 99.1 90.3\n```\n:::\n:::\n\n\n\nwe are back where we started.\n\n## Make wider 2/2\n\nOr\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n pivot_wider(names_from=pig, values_from=weight)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 6\n feed `1` `2` `3` `4` `5`\n \n1 feed1 60.8 57 65 58.6 61.7\n2 feed2 68.7 67.7 74 66.3 69.8\n3 feed3 92.6 92.1 90.2 96.5 99.1\n4 feed4 87.9 84.2 83.1 85.7 90.3\n```\n:::\n:::\n\n\n\n## Disease presence and absence at two locations\n\nFrequencies of plants observed with and without disease at two\nlocations:\n\n``` \nSpecies Disease present Disease absent\n Location X Location Y Location X Location Y\nA 44 12 38 10\nB 28 22 20 18\n```\n\nThis has two rows of headers, so I rewrote the data file:\n\n``` \nSpecies present_x present_y absent_x absent_y\nA 44 12 38 10\nB 28 22 20 18\n```\n\nRead into data frame called `prevalence`.\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n Species present_x present_y absent_x absent_y\n \n1 A 44 12 38 10\n2 B 28 22 20 18\n```\n:::\n:::\n\n\n\n## Lengthen and separate\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to = \"column\", \n values_to = \"freq\") %>% \n separate_wider_delim(column, \"_\", \n names = c(\"disease\", \"location\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Species disease location freq\n \n1 A present x 44\n2 A present y 12\n3 A absent x 38\n4 A absent y 10\n5 B present x 28\n6 B present y 22\n7 B absent x 20\n8 B absent y 18\n```\n:::\n:::\n\n\n\n## Making longer, the better way\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence %>% \n pivot_longer(-Species, names_to=c(\"disease\", \"location\"),\n names_sep=\"_\", \n values_to=\"frequency\") -> prevalence_longer \nprevalence_longer\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 4\n Species disease location frequency\n \n1 A present x 44\n2 A present y 12\n3 A absent x 38\n4 A absent y 10\n5 B present x 28\n6 B present y 22\n7 B absent x 20\n8 B absent y 18\n```\n:::\n:::\n\n\n\n## Making wider, different ways\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=c(Species, location), values_from=frequency)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n disease A_x A_y B_x B_y\n \n1 present 44 12 28 22\n2 absent 38 10 20 18\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nprevalence_longer %>% \n pivot_wider(names_from=location, values_from=frequency)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 4\n Species disease x y\n \n1 A present 44 12\n2 A absent 38 10\n3 B present 28 22\n4 B absent 20 18\n```\n:::\n:::\n\n\n\n## Interlude {.smaller}\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(weight_mean=mean(weight))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n feed weight_mean\n \n1 feed1 60.6\n2 feed2 69.3\n3 feed3 94.1\n4 feed4 86.2\n```\n:::\n:::\n\n\n\n## What if summary is more than one number?\n\neg. quartiles:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=quantile(weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n# Groups: feed [4]\n feed r\n \n1 feed1 58.6\n2 feed1 61.7\n3 feed2 67.7\n4 feed2 69.8\n5 feed3 92.1\n6 feed3 96.5\n7 feed4 84.2\n8 feed4 87.9\n```\n:::\n:::\n\n\n\n## this also works\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(quantile(weight, c(0.25, 0.75)))) %>% \n unnest(r)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n feed r\n \n1 feed1 58.6\n2 feed1 61.7\n3 feed2 67.7\n4 feed2 69.8\n5 feed3 92.1\n6 feed3 96.5\n7 feed4 84.2\n8 feed4 87.9\n```\n:::\n:::\n\n\n\n## or, even better, use `enframe`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nquantile(pigs_longer$weight, c(0.25, 0.75))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n 25% 75% \n65.975 90.225 \n```\n:::\n\n```{.r .cell-code}\nenframe(quantile(pigs_longer$weight, c(0.25, 0.75)))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n name value\n \n1 25% 66.0\n2 75% 90.2\n```\n:::\n:::\n\n\n\n## A nice look\n\nRun this one line at a time to see how it works:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs_longer %>% \n group_by(feed) %>% \n summarize(r=list(enframe(quantile(weight, c(0.25, 0.75))))) %>% \n unnest(r) %>% \n pivot_wider(names_from=name, values_from=value) \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 3\n feed `25%` `75%`\n \n1 feed1 58.6 61.7\n2 feed2 67.7 69.8\n3 feed3 92.1 96.5\n4 feed4 84.2 87.9\n```\n:::\n:::\n\n\n\n## A hairy one\n\n18 people receive one of three treatments. At 3 different times (pre,\npost, followup) two variables `y` and `z` are measured on each person:\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 18 x 8\n treatment rep pre_y post_y fu_y pre_z post_z fu_z\n \n 1 A 1 3 13 9 0 0 9\n 2 A 2 0 14 10 6 6 3\n 3 A 3 4 6 17 8 2 6\n 4 A 4 7 7 13 7 6 4\n 5 A 5 3 12 11 6 12 6\n 6 A 6 10 14 8 13 3 8\n 7 B 1 9 11 17 8 11 27\n 8 B 2 4 16 13 9 3 26\n 9 B 3 8 10 9 12 0 18\n10 B 4 5 9 13 3 0 14\n11 B 5 0 15 11 3 0 25\n12 B 6 4 11 14 4 2 9\n13 Control 1 10 12 15 4 3 7\n14 Control 2 2 8 12 8 7 20\n15 Control 3 4 9 10 2 0 10\n16 Control 4 10 8 8 5 8 14\n17 Control 5 11 11 11 1 0 11\n18 Control 6 1 5 15 8 9 10\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 18 x 8\n id treatment pre_y post_y fu_y pre_z post_z fu_z\n \n 1 A.1 A 3 13 9 0 0 9\n 2 A.2 A 0 14 10 6 6 3\n 3 A.3 A 4 6 17 8 2 6\n 4 A.4 A 7 7 13 7 6 4\n 5 A.5 A 3 12 11 6 12 6\n 6 A.6 A 10 14 8 13 3 8\n 7 B.1 B 9 11 17 8 11 27\n 8 B.2 B 4 16 13 9 3 26\n 9 B.3 B 8 10 9 12 0 18\n10 B.4 B 5 9 13 3 0 14\n11 B.5 B 0 15 11 3 0 25\n12 B.6 B 4 11 14 4 2 9\n13 Control.1 Control 10 12 15 4 3 7\n14 Control.2 Control 2 8 12 8 7 20\n15 Control.3 Control 4 9 10 2 0 10\n16 Control.4 Control 10 8 8 5 8 14\n17 Control.5 Control 11 11 11 1 0 11\n18 Control.6 Control 1 5 15 8 9 10\n```\n:::\n:::\n\n\n\n## Attempt 1\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \"var\"),\n names_sep=\"_\",\n values_to = \"vvv\"\n )\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 108 x 5\n id treatment time var vvv\n \n 1 A.1 A pre y 3\n 2 A.1 A post y 13\n 3 A.1 A fu y 9\n 4 A.1 A pre z 0\n 5 A.1 A post z 0\n 6 A.1 A fu z 9\n 7 A.2 A pre y 0\n 8 A.2 A post y 14\n 9 A.2 A fu y 10\n10 A.2 A pre z 6\n# i 98 more rows\n```\n:::\n:::\n\n\n\nThis is *too* long! We wanted a column called `y` and a column called\n`z`, but they have been pivoted-longer too.\n\n## Attempt 2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes %>% pivot_longer(contains(\"_\"),\n names_to=c(\"time\", \".value\"),\n names_sep=\"_\"\n ) -> repmes3\nrepmes3\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 54 x 5\n id treatment time y z\n \n 1 A.1 A pre 3 0\n 2 A.1 A post 13 0\n 3 A.1 A fu 9 9\n 4 A.2 A pre 0 6\n 5 A.2 A post 14 6\n 6 A.2 A fu 10 3\n 7 A.3 A pre 4 8\n 8 A.3 A post 6 2\n 9 A.3 A fu 17 6\n10 A.4 A pre 7 7\n# i 44 more rows\n```\n:::\n:::\n\n\n\nThis has done what we wanted.\n\n## make a graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(repmes3, aes(x=fct_inorder(time), y=y, \n colour=treatment, group=id)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-beamer/tidy-extra-R-19-1.pdf)\n:::\n:::\n\n\n\nA so-called spaghetti plot. The three measurements for each person are\njoined by lines, and the lines are coloured by treatment.\n\n## or do the plot with means\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrepmes3 %>% group_by(treatment, ftime=fct_inorder(time)) %>% \n summarize(mean_y=mean(y)) %>% \n ggplot(aes(x=ftime, y=mean_y, colour=treatment, \n group=treatment)) + \n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](tidy_extra_files/figure-beamer/tidy-extra-R-20-1.pdf)\n:::\n:::\n\n\n\nOn average, the two real treatments go up and level off, but the control\ngroup is very different.\n", "supporting": [ "tidy_extra_files/figure-beamer" ], diff --git a/_freeze/tidy_extra/figure-beamer/tidy-extra-R-19-1.pdf b/_freeze/tidy_extra/figure-beamer/tidy-extra-R-19-1.pdf index 2078fc1..92b3088 100644 Binary files a/_freeze/tidy_extra/figure-beamer/tidy-extra-R-19-1.pdf and b/_freeze/tidy_extra/figure-beamer/tidy-extra-R-19-1.pdf differ diff --git a/_freeze/tidy_extra/figure-beamer/tidy-extra-R-20-1.pdf b/_freeze/tidy_extra/figure-beamer/tidy-extra-R-20-1.pdf index e6341d2..23ec063 100644 Binary files a/_freeze/tidy_extra/figure-beamer/tidy-extra-R-20-1.pdf and b/_freeze/tidy_extra/figure-beamer/tidy-extra-R-20-1.pdf differ diff --git a/_freeze/tidying/execute-results/html.json b/_freeze/tidying/execute-results/html.json index b081efb..b3a3e22 100644 --- a/_freeze/tidying/execute-results/html.json +++ b/_freeze/tidying/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "0aaa6e8b3c280b75cfc55e35e51174a5", + "hash": "448a743f257c4868fdb4a3d8b5602bd6", "result": { - "markdown": "---\ntitle: \"Tidying data\"\n---\n\n\n## Tidying data {.scrollable}\n- Data rarely come to us as we want to use them.\n- Before we can do analysis, typically have organizing to do.\n- This is typical of ANOVA-type data, “wide format”:\n\n```\n pig feed1 feed2 feed3 feed4\n 1 60.8 68.7 92.6 87.9\n 2 57.0 67.7 92.1 84.2\n 3 65.0 74.0 90.2 83.1\n 4 58.6 66.3 96.5 85.7\n 5 61.7 69.8 99.1 90.3\n```\n\n- 20 pigs randomly allocated to one of four feeds. At end of \nstudy, weight of each pig is recorded. \n- Are any differences in mean weights among the feeds?\n- Problem: want all weights in one column, with 2nd column\nlabelling which feed. Untidy!\n\n\n## Tidy and untidy data (Wickham) {.scrollable}\n- Data set easier to deal with if:\n - each observation is one row\n - each variable is one column\n - each type of observation unit is one table\n- Data arranged this way called “tidy”; otherwise called “untidy”.\n- For the pig data:\n - response variable is weight, but scattered over 4\ncolumns, which are levels of a factor `feed`.\n - Want all the weights in one column, with a second column `feed`\nsaying which feed that weight goes with.\n - Then we can run `aov`.\n \n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n \n## Reading in the pig data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs1.txt\"\npigs1 <- read_delim(my_url, \" \")\npigs1\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making it longer\n\n- We wanted all the weights in one column, labelled by which feed they went with.\n- This is a very common reorganization, and the magic “verb” is\n`pivot_longer`:\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(feed1:feed4, names_to=\"feed\", \n values_to=\"weight\") -> pigs2\npigs2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- `pigs2` now in “long” format, ready for analysis. \n- Anatomy of `pivot_longer`: \n - columns to combine\n - a name for column that will contain groups (\"names\")\n - a name for column that will contain measurements (\"values\")\n\n\n## Identifying the pigs\n\n- Values in `pig` identify pigs *within each group*: pig 1 is four different pigs!\n- Create unique pig IDs by gluing pig number onto feed:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>% mutate(pig_id=str_c(feed, \"_\", pig)) -> pigs2\npigs2 %>% slice_sample(n=7)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## ...and finally, the analysis\n- which is just what we saw before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweight.1 <- aov(weight ~ feed, data = pigs2)\nsummary(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.8 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n- The mean weights of pigs on the different feeds are definitely not all\nequal.\n- So we run Tukey to see which ones differ (over).\n\n## Tukey\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = weight ~ feed, data = pigs2)\n\n$feed\n diff lwr upr p adj\nfeed2-feed1 8.68 3.001038 14.358962 0.0024000\nfeed3-feed1 33.48 27.801038 39.158962 0.0000000\nfeed4-feed1 25.62 19.941038 31.298962 0.0000000\nfeed3-feed2 24.80 19.121038 30.478962 0.0000000\nfeed4-feed2 16.94 11.261038 22.618962 0.0000013\nfeed4-feed3 -7.86 -13.538962 -2.181038 0.0055599\n```\n:::\n:::\n\n\nAll of the feeds differ! \n\n## Mean weights by feed \n\nTo find the best and worst, get mean weight by \nfeed group. I borrowed an idea from earlier to put the means in descending order:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>%\n group_by(feed) %>%\n summarize(mean_weight = mean(weight))%>%\n arrange(desc(mean_weight))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nFeed 3 is best, feed 1 worst.\n\n## Should we have any concerns about the ANOVA?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(pigs2, aes(x = feed, y = weight)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-revealjs/pigfeedplot-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Feed 2 has an outlier\n- But there are only 5 pigs in each group\n- The\nconclusion is so clear that I am OK with this.\n\n\n## Tuberculosis\n\n- The World Health Organization keeps track of number of cases of\nvarious diseases, eg. tuberculosis.\n- Some data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/tb.csv\"\ntb <- read_csv(my_url)\n```\n:::\n\n\n\n## The data (randomly chosen rows)\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% slice_sample(n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nMany rows:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnrow(tb)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5769\n```\n:::\n:::\n\n\n\n## What we have\n\n- Variables: country (abbreviated), year. Then number of\ncases for each gender and age group, eg. `m1524` is males aged 15–24.\nAlso `mu` and `fu`, where age is unknown.\n- Lots of missings. Want to get rid of.\n- Abbreviations [here](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% \n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) -> tb2\n```\n:::\n\n\n- Code for `pivot_longer`:\n - columns to make longer\n - column to contain the names (categorical)\n - column to contain the values (quantitative)\n - drop missings in the values\n\n## Results (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Separating\n- 4 columns, but 5 variables, since `genage` contains both gender and\nage group. Split that up using separate.\n- `separate` needs 3 things:\n - what to separate (no quotes needed),\n - what to separate into (here you do need quotes),\n - how to split.\n- For “how to split”, here “after first character”:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2 %>% separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\") -> tb3\ntb3\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Tidied tuberculosis data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n::: {.cell}\n\n:::\n\n\n\n## In practice... {.smaller }\n\n- instead of doing the pipe one step at a time, you *debug* it one step at\na time, and when you have each step working, you use that step’s\noutput as input to the next step, thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>%\n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) %>% \n separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\")\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- You can split the R code over as many lines as you like, as long as\neach line is incomplete, so that R knows more is to come.\n- I like to put the pipe symbol on the end of the line.\n\n## Total tuberculosis cases by year (some of the years)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>%\n filter(between(year, 1991, 1998)) %>% \n group_by(year) %>% summarize(total=sum(freq))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Something very interesting happened between 1994 and 1995.\n\n## To find out what\n\n- try counting up total cases by country:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(total=sum(freq)) %>% \n arrange(desc(total))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## What years do I have for China?\n\nChina started recording in 1995, which is at least part of the problem:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% filter(iso2==\"CN\") %>% \n group_by(year) %>% \n summarize(total=sum(freq))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## First year of recording by country?\n\n- A lot of countries started recording in about 1995, in fact:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(first_year=min(year)) %>% \n count(first_year)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- So the reason for the big jump in cases is that so many countries started recording then, not that there really were more cases.\n\n## Some Toronto weather data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/STAC32/toronto_weather.csv\"\nweather <- read_csv(my_url)\nweather\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## The columns\n\n- Daily weather records for \"Toronto City\" weather station in 2018:\n\n - `station`: identifier for this weather station (always same here)\n - `Year`, `Month`\n - `element`: whether temperature given was daily max or daily min\n - `d01`, `d02`,... `d31`: day of the month from 1st to 31st.\n\n## Off we go\n\nNumbers in data frame all temperatures (for different days of the month),\nso first step is\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>% \n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## `Element`\n\n- Column `element` contains names of two different variables, that\nshould each be in separate column.\n- Distinct from eg. `m1524` in tuberculosis data, that contained levels of\ntwo different factors, handled by separate.\n- Untangling names of variables handled by `pivot_wider`.\n\n## Handling `element`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE) %>% \n pivot_wider(names_from=element, \n values_from=temperature) \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n\n## Further improvements 1/2\n- We have tidy data now, but can improve things further.\n- `mutate` creates new columns from old (or assign back to change a\nvariable).\n- Would like numerical dates. `separate` works, or pull out number as\nbelow.\n- `select` keeps columns (or drops, with minus). Station name has no\nvalue to us.\n\n\n## Further improvements 2/2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Final step(s)\n- Make year-month-day into proper date.\n- Keep only date, tmax, tmin:\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station) %>% \n unite(datestr, c(Year, Month, Day), sep = \"-\") %>%\n mutate(date = as.Date(datestr)) %>%\n select(c(date, tmax, tmin)) -> weather_tidy\n```\n:::\n\n\\normalsize\n\n## Our tidy data frame\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Plotting the temperatures\n- Plot temperature against date joined by lines, but with separate lines\nfor max and min. `ggplot` requires something like\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(..., aes(x = date, y = temperature)) + geom_point() + \n geom_line()\n```\n:::\n\n\\normalsize\n\nonly we have two temperatures, one a max and one a min, that we\nwant to keep separate.\n\n- The trick: combine `tmax` and `tmin` together into one column, keeping\ntrack of what kind of temp they are. (This actually same format as\nuntidy `weather`.) Are making `weather_tidy`\nuntidy for purposes of drawing graph only.\n- Then can do something like\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(d, aes(x = date, y = temperature, colour = maxmin)) \n + geom_point() + geom_line()\n```\n:::\n\n\\normalsize\nto distinguish max and min on graph.\n\n## Setting up plot\n- Since we only need data frame for plot, we can do the\ncolumn-creation and plot in a pipeline.\n- For a `ggplot` in a pipeline, the initial data frame is omitted, because it\nis whatever came out of the previous step.\n- To make those “one column”s: `pivot_longer`. I save the graph to show overleaf:\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy %>%\n pivot_longer(tmax:tmin, names_to=\"maxmin\", \n values_to=\"temperature\") %>%\n ggplot(aes(x = date, y = temperature, colour = maxmin)) +\n geom_line() -> g\n```\n:::\n\n\n## The plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-revealjs/temp-plot-1.png){width=960}\n:::\n:::\n\n\n## Summary of tidying “verbs”\n\n |Verb | Purpose|\n |:-----|:-------------------------------|\n |`pivot_longer` | Combine columns that measure same thing into one |\n |`pivot_wider` | Take column that measures one thing under different conditions and put into multiple columns |\n |`separate` | Turn a column that encodes several variables into several columns |\n |`unite` | Combine several (related) variables into one \"combination\" variable |\n\n `pivot_longer` and `pivot_wider` are opposites; `separate` and\n `unite` are opposites.\n \n\n\n", + "markdown": "---\ntitle: \"Tidying data\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Tidying data {.scrollable}\n\n- Data rarely come to us as we want to use them.\n- Before we can do analysis, typically have organizing to do.\n- This is typical of ANOVA-type data, \"wide format\":\n\n``` \n pig feed1 feed2 feed3 feed4\n 1 60.8 68.7 92.6 87.9\n 2 57.0 67.7 92.1 84.2\n 3 65.0 74.0 90.2 83.1\n 4 58.6 66.3 96.5 85.7\n 5 61.7 69.8 99.1 90.3\n```\n\n- 20 pigs randomly allocated to one of four feeds. At end of study,\n weight of each pig is recorded.\n- Are any differences in mean weights among the feeds?\n- Problem: want all weights in one column, with 2nd column labelling\n which feed. Untidy!\n\n## Tidy and untidy data (Wickham) {.scrollable}\n\n- Data set easier to deal with if:\n - each observation is one row\n - each variable is one column\n - each type of observation unit is one table\n- Data arranged this way called \"tidy\"; otherwise called \"untidy\".\n- For the pig data:\n - response variable is weight, but scattered over 4 columns, which\n are levels of a factor `feed`.\n - Want all the weights in one column, with a second column `feed`\n saying which feed that weight goes with.\n - Then we can run `aov`.\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## Reading in the pig data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs1.txt\"\npigs1 <- read_delim(my_url, \" \")\npigs1\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making it longer\n\n- We wanted all the weights in one column, labelled by which feed they\n went with.\n- This is a very common reorganization, and the magic \"verb\" is\n `pivot_longer`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(feed1:feed4, names_to=\"feed\", \n values_to=\"weight\") -> pigs2\npigs2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Alternatives\n\nAny way of choosing the columns to pivot longer is good, eg:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(-pig, names_to=\"feed\", \n values_to=\"weight\") \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nor\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(starts_with(\"feed\"), names_to=\"feed\", \n values_to=\"weight\") \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- `pigs2` now in \"long\" format, ready for analysis.\n- Anatomy of `pivot_longer`:\n - columns to combine\n - a name for column that will contain groups (\"names\")\n - a name for column that will contain measurements (\"values\")\n\n## Identifying the pigs\n\n- Values in `pig` identify pigs *within each group*: pig 1 is four\n different pigs!\n- Create unique pig IDs by gluing pig number onto feed:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>% mutate(pig_id=str_c(feed, \"_\", pig)) -> pigs2\npigs2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## ...and finally, the analysis\n\n- which is just what we saw before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweight.1 <- aov(weight ~ feed, data = pigs2)\nsummary(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.8 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n- The mean weights of pigs on the different feeds are definitely not\n all equal.\n- So we run Tukey to see which ones differ (over).\n\n## Tukey\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = weight ~ feed, data = pigs2)\n\n$feed\n diff lwr upr p adj\nfeed2-feed1 8.68 3.001038 14.358962 0.0024000\nfeed3-feed1 33.48 27.801038 39.158962 0.0000000\nfeed4-feed1 25.62 19.941038 31.298962 0.0000000\nfeed3-feed2 24.80 19.121038 30.478962 0.0000000\nfeed4-feed2 16.94 11.261038 22.618962 0.0000013\nfeed4-feed3 -7.86 -13.538962 -2.181038 0.0055599\n```\n:::\n:::\n\n\nAll of the feeds differ!\n\n## Mean weights by feed\n\nTo find the best and worst, get mean weight by feed group. I borrowed an\nidea from earlier to put the means in descending order:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>%\n group_by(feed) %>%\n summarize(mean_weight = mean(weight))%>%\n arrange(desc(mean_weight))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nFeed 3 is best, feed 1 worst.\n\n## Should we have any concerns about the ANOVA?\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(pigs2, aes(x = feed, y = weight)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-revealjs/pigfeedplot-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Feed 2 has an outlier\n- But there are only 5 pigs in each group\n- The conclusion is so clear that I am OK with this.\n\n## Tuberculosis\n\n- The World Health Organization keeps track of number of cases of\n various diseases, eg. tuberculosis.\n- Some data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/tb.csv\"\ntb <- read_csv(my_url)\n```\n:::\n\n\n## The data (randomly chosen rows)\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% slice_sample(n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\nMany rows:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnrow(tb)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5769\n```\n:::\n:::\n\n\n## What we have\n\n- Variables: country (abbreviated), year. Then number of cases for\n each gender and age group, eg. `m1524` is males aged 15--24. Also\n `mu` and `fu`, where age is unknown.\n- Lots of missings. Want to get rid of.\n- Abbreviations\n [here](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% \n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) -> tb2\n```\n:::\n\n\n- Code for `pivot_longer`:\n - columns to make longer\n - column to contain the names (categorical)\n - column to contain the values (quantitative)\n - drop missings in the values\n\n## Results (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Separating\n\n- 4 columns, but 5 variables, since `genage` contains both gender and\n age group. Split that up using separate.\n- `separate` needs 3 things:\n - what to separate (no quotes needed),\n - what to separate into (here you do need quotes),\n - how to split.\n- For \"how to split\", here \"after first character\":\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2 %>% separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\") -> tb3\ntb3\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Tidied tuberculosis data (some)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n::: {.cell}\n\n:::\n\n\n## In practice... {.smaller}\n\n- instead of doing the pipe one step at a time, you *debug* it one\n step at a time, and when you have each step working, you use that\n step's output as input to the next step, thus:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>%\n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) %>% \n separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\")\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- You can split the R code over as many lines as you like, as long as\n each line is incomplete, so that R knows more is to come.\n- I like to put the pipe symbol on the end of the line.\n\n## Total tuberculosis cases by year (some of the years)\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>%\n filter(between(year, 1991, 1998)) %>% \n group_by(year) %>% summarize(total=sum(freq))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Something very interesting happened between 1994 and 1995.\n\n## To find out what\n\n- try counting up total cases by country:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(total=sum(freq)) %>% \n arrange(desc(total))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## What years do I have for China?\n\nChina started recording in 1995, which is at least part of the problem:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% filter(iso2==\"CN\") %>% \n group_by(year) %>% \n summarize(total=sum(freq))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## First year of recording by country?\n\n- A lot of countries started recording in about 1995, in fact:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(first_year=min(year)) %>% \n count(first_year)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- So the reason for the big jump in cases is that so many countries\n started recording then, not that there really were more cases.\n\n## Some Toronto weather data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/STAC32/toronto_weather.csv\"\nweather <- read_csv(my_url)\nweather\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## The columns\n\n- Daily weather records for \"Toronto City\" weather station in 2018:\n\n - `station`: identifier for this weather station (always same\n here)\n - `Year`, `Month`\n - `element`: whether temperature given was daily max or daily min\n - `d01`, `d02`,... `d31`: day of the month from 1st to 31st.\n\n## Off we go\n\nNumbers in data frame all temperatures (for different days of the\nmonth), so first step is\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>% \n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## `Element`\n\n- Column `element` contains names of two different variables, that\n should each be in separate column.\n- Distinct from eg. `m1524` in tuberculosis data, that contained\n levels of two different factors, handled by separate.\n- Untangling names of variables handled by `pivot_wider`.\n\n## Handling `element`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE) %>% \n pivot_wider(names_from=element, \n values_from=temperature) \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Further improvements 1/2\n\n- We have tidy data now, but can improve things further.\n- `mutate` creates new columns from old (or assign back to change a\n variable).\n- Would like numerical dates. `separate` works, or pull out number as\n below.\n- `select` keeps columns (or drops, with minus). Station name has no\n value to us.\n\n## Further improvements 2/2\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Final step(s)\n\n- Make year-month-day into proper date.\n- Keep only date, tmax, tmin:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station) %>% \n unite(datestr, c(Year, Month, Day), sep = \"-\") %>%\n mutate(date = as.Date(datestr)) %>%\n select(c(date, tmax, tmin)) -> weather_tidy\n```\n:::\n\n\n## Our tidy data frame\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Plotting the temperatures\n\n- Plot temperature against date joined by lines, but with separate\n lines for max and min. `ggplot` requires something like\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(..., aes(x = date, y = temperature)) + geom_point() + \n geom_line()\n```\n:::\n\n\n\\normalsize\n\nonly we have two temperatures, one a max and one a min, that we want to\nkeep separate.\n\n- The trick: combine `tmax` and `tmin` together into one column,\n keeping track of what kind of temp they are. (This actually same\n format as untidy `weather`.) Are making `weather_tidy` untidy for\n purposes of drawing graph only.\n- Then can do something like\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(d, aes(x = date, y = temperature, colour = maxmin)) \n + geom_point() + geom_line()\n```\n:::\n\n\n\\normalsize\n\nto distinguish max and min on graph.\n\n## Setting up plot\n\n- Since we only need data frame for plot, we can do the\n column-creation and plot in a pipeline.\n- For a `ggplot` in a pipeline, the initial data frame is omitted,\n because it is whatever came out of the previous step.\n- To make those \"one column\"s: `pivot_longer`. I save the graph to\n show overleaf:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy %>%\n pivot_longer(tmax:tmin, names_to=\"maxmin\", \n values_to=\"temperature\") %>%\n ggplot(aes(x = date, y = temperature, colour = maxmin)) + geom_point() +\n geom_line() -> g\n```\n:::\n\n\n## The plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-revealjs/temp-plot-1.png){width=960}\n:::\n:::\n\n\n## Summary of tidying \"verbs\"\n\n| Verb | Purpose |\n|:---------------|:---------------------------------------------------------------------------------------------|\n| `pivot_longer` | Combine columns that measure same thing into one |\n| `pivot_wider` | Take column that measures one thing under different conditions and put into multiple columns |\n| `separate` | Turn a column that encodes several variables into several columns |\n| `unite` | Combine several (related) variables into one \"combination\" variable |\n\n`pivot_longer` and `pivot_wider` are opposites; `separate` and `unite`\nare opposites.\n", "supporting": [ "tidying_files/figure-revealjs" ], diff --git a/_freeze/tidying/execute-results/tex.json b/_freeze/tidying/execute-results/tex.json index c680bae..4bd21da 100644 --- a/_freeze/tidying/execute-results/tex.json +++ b/_freeze/tidying/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "0aaa6e8b3c280b75cfc55e35e51174a5", + "hash": "448a743f257c4868fdb4a3d8b5602bd6", "result": { - "markdown": "---\ntitle: \"Tidying data\"\n---\n\n\n\n## Tidying data {.scrollable}\n- Data rarely come to us as we want to use them.\n- Before we can do analysis, typically have organizing to do.\n- This is typical of ANOVA-type data, “wide format”:\n\n```\n pig feed1 feed2 feed3 feed4\n 1 60.8 68.7 92.6 87.9\n 2 57.0 67.7 92.1 84.2\n 3 65.0 74.0 90.2 83.1\n 4 58.6 66.3 96.5 85.7\n 5 61.7 69.8 99.1 90.3\n```\n\n- 20 pigs randomly allocated to one of four feeds. At end of \nstudy, weight of each pig is recorded. \n- Are any differences in mean weights among the feeds?\n- Problem: want all weights in one column, with 2nd column\nlabelling which feed. Untidy!\n\n\n## Tidy and untidy data (Wickham) {.scrollable}\n- Data set easier to deal with if:\n - each observation is one row\n - each variable is one column\n - each type of observation unit is one table\n- Data arranged this way called “tidy”; otherwise called “untidy”.\n- For the pig data:\n - response variable is weight, but scattered over 4\ncolumns, which are levels of a factor `feed`.\n - Want all the weights in one column, with a second column `feed`\nsaying which feed that weight goes with.\n - Then we can run `aov`.\n \n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n \n## Reading in the pig data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs1.txt\"\npigs1 <- read_delim(my_url, \" \")\npigs1\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n pig feed1 feed2 feed3 feed4\n \n1 1 60.8 68.7 92.6 87.9\n2 2 57 67.7 92.1 84.2\n3 3 65 74 90.2 83.1\n4 4 58.6 66.3 96.5 85.7\n5 5 61.7 69.8 99.1 90.3\n```\n:::\n:::\n\n\n\n## Making it longer\n\n- We wanted all the weights in one column, labelled by which feed they went with.\n- This is a very common reorganization, and the magic “verb” is\n`pivot_longer`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(feed1:feed4, names_to=\"feed\", \n values_to=\"weight\") -> pigs2\npigs2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Comments\n\n- `pigs2` now in “long” format, ready for analysis. \n- Anatomy of `pivot_longer`: \n - columns to combine\n - a name for column that will contain groups (\"names\")\n - a name for column that will contain measurements (\"values\")\n\n\n## Identifying the pigs\n\n- Values in `pig` identify pigs *within each group*: pig 1 is four different pigs!\n- Create unique pig IDs by gluing pig number onto feed:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>% mutate(pig_id=str_c(feed, \"_\", pig)) -> pigs2\npigs2 %>% slice_sample(n=7)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 4\n pig feed weight pig_id \n \n1 2 feed1 57 feed1_2\n2 4 feed1 58.6 feed1_4\n3 3 feed3 90.2 feed3_3\n4 1 feed4 87.9 feed4_1\n5 1 feed2 68.7 feed2_1\n6 5 feed2 69.8 feed2_5\n7 5 feed4 90.3 feed4_5\n```\n:::\n:::\n\n\n\n\n## ...and finally, the analysis\n- which is just what we saw before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweight.1 <- aov(weight ~ feed, data = pigs2)\nsummary(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.8 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n- The mean weights of pigs on the different feeds are definitely not all\nequal.\n- So we run Tukey to see which ones differ (over).\n\n## Tukey\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = weight ~ feed, data = pigs2)\n\n$feed\n diff lwr upr p adj\nfeed2-feed1 8.68 3.001038 14.358962 0.0024000\nfeed3-feed1 33.48 27.801038 39.158962 0.0000000\nfeed4-feed1 25.62 19.941038 31.298962 0.0000000\nfeed3-feed2 24.80 19.121038 30.478962 0.0000000\nfeed4-feed2 16.94 11.261038 22.618962 0.0000013\nfeed4-feed3 -7.86 -13.538962 -2.181038 0.0055599\n```\n:::\n:::\n\n\n\nAll of the feeds differ! \n\n## Mean weights by feed \n\nTo find the best and worst, get mean weight by \nfeed group. I borrowed an idea from earlier to put the means in descending order:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>%\n group_by(feed) %>%\n summarize(mean_weight = mean(weight))%>%\n arrange(desc(mean_weight))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n feed mean_weight\n \n1 feed3 94.1\n2 feed4 86.2\n3 feed2 69.3\n4 feed1 60.6\n```\n:::\n:::\n\n\n\nFeed 3 is best, feed 1 worst.\n\n## Should we have any concerns about the ANOVA?\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(pigs2, aes(x = feed, y = weight)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-beamer/pigfeedplot-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Feed 2 has an outlier\n- But there are only 5 pigs in each group\n- The\nconclusion is so clear that I am OK with this.\n\n\n## Tuberculosis\n\n- The World Health Organization keeps track of number of cases of\nvarious diseases, eg. tuberculosis.\n- Some data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/tb.csv\"\ntb <- read_csv(my_url)\n```\n:::\n\n\n\n\n## The data (randomly chosen rows)\n\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% slice_sample(n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 22\n iso2 year m04 m514 m014 m1524 m2534 m3544 m4554 m5564 m65\n \n 1 MW 1989 NA NA NA NA NA NA NA NA NA\n 2 SA 1989 NA NA NA NA NA NA NA NA NA\n 3 IN 1982 NA NA NA NA NA NA NA NA NA\n 4 QA 2001 NA NA 1 2 0 3 4 0 3\n 5 BG 1998 NA NA NA NA NA NA NA NA NA\n 6 PH 1984 NA NA NA NA NA NA NA NA NA\n 7 ZA 2007 340 594 1909 10514 21948 20076 12164 4792 2021\n 8 CI 1986 NA NA NA NA NA NA NA NA NA\n 9 TK 2001 NA NA NA NA NA NA NA NA NA\n10 BJ 1993 NA NA NA NA NA NA NA NA NA\n# i 11 more variables: mu , f04 , f514 , f014 ,\n# f1524 , f2534 , f3544 , f4554 , f5564 ,\n# f65 , fu \n```\n:::\n:::\n\n\n\nMany rows:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnrow(tb)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5769\n```\n:::\n:::\n\n\n\n\n## What we have\n\n- Variables: country (abbreviated), year. Then number of\ncases for each gender and age group, eg. `m1524` is males aged 15–24.\nAlso `mu` and `fu`, where age is unknown.\n- Lots of missings. Want to get rid of.\n- Abbreviations [here](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% \n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) -> tb2\n```\n:::\n\n\n\n- Code for `pivot_longer`:\n - columns to make longer\n - column to contain the names (categorical)\n - column to contain the values (quantitative)\n - drop missings in the values\n\n## Results (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 4\n iso2 year genage freq\n \n 1 AD 1996 m014 0\n 2 AD 1996 m1524 0\n 3 AD 1996 m2534 0\n 4 AD 1996 m3544 4\n 5 AD 1996 m4554 1\n 6 AD 1996 m5564 0\n 7 AD 1996 m65 0\n 8 AD 1996 f014 0\n 9 AD 1996 f1524 1\n10 AD 1996 f2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n\n\n## Separating\n- 4 columns, but 5 variables, since `genage` contains both gender and\nage group. Split that up using separate.\n- `separate` needs 3 things:\n - what to separate (no quotes needed),\n - what to separate into (here you do need quotes),\n - how to split.\n- For “how to split”, here “after first character”:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2 %>% separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\") -> tb3\ntb3\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 5\n iso2 year gender age freq\n \n 1 AD 1996 m 014 0\n 2 AD 1996 m 1524 0\n 3 AD 1996 m 2534 0\n 4 AD 1996 m 3544 4\n 5 AD 1996 m 4554 1\n 6 AD 1996 m 5564 0\n 7 AD 1996 m 65 0\n 8 AD 1996 f 014 0\n 9 AD 1996 f 1524 1\n10 AD 1996 f 2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n\n\n## Tidied tuberculosis data (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 5\n iso2 year gender age freq\n \n 1 AD 1996 m 014 0\n 2 AD 1996 m 1524 0\n 3 AD 1996 m 2534 0\n 4 AD 1996 m 3544 4\n 5 AD 1996 m 4554 1\n 6 AD 1996 m 5564 0\n 7 AD 1996 m 65 0\n 8 AD 1996 f 014 0\n 9 AD 1996 f 1524 1\n10 AD 1996 f 2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n::: {.cell}\n\n:::\n\n\n\n\n## In practice... {.smaller }\n\n- instead of doing the pipe one step at a time, you *debug* it one step at\na time, and when you have each step working, you use that step’s\noutput as input to the next step, thus:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>%\n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) %>% \n separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 5\n iso2 year gender age freq\n \n 1 AD 1996 m 014 0\n 2 AD 1996 m 1524 0\n 3 AD 1996 m 2534 0\n 4 AD 1996 m 3544 4\n 5 AD 1996 m 4554 1\n 6 AD 1996 m 5564 0\n 7 AD 1996 m 65 0\n 8 AD 1996 f 014 0\n 9 AD 1996 f 1524 1\n10 AD 1996 f 2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n\n\n- You can split the R code over as many lines as you like, as long as\neach line is incomplete, so that R knows more is to come.\n- I like to put the pipe symbol on the end of the line.\n\n## Total tuberculosis cases by year (some of the years)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>%\n filter(between(year, 1991, 1998)) %>% \n group_by(year) %>% summarize(total=sum(freq))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n year total\n \n1 1991 544\n2 1992 512\n3 1993 492\n4 1994 750\n5 1995 513971\n6 1996 635705\n7 1997 733204\n8 1998 840389\n```\n:::\n:::\n\n\n\n- Something very interesting happened between 1994 and 1995.\n\n## To find out what\n\n- try counting up total cases by country:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(total=sum(freq)) %>% \n arrange(desc(total))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 213 x 2\n iso2 total\n \n 1 CN 4065174\n 2 IN 3966169\n 3 ID 1129015\n 4 ZA 900349\n 5 BD 758008\n 6 VN 709695\n 7 CD 603095\n 8 PH 490040\n 9 BR 440609\n10 KE 431523\n# i 203 more rows\n```\n:::\n:::\n\n\n\n## What years do I have for China?\n\nChina started recording in 1995, which is at least part of the problem:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% filter(iso2==\"CN\") %>% \n group_by(year) %>% \n summarize(total=sum(freq))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 14 x 2\n year total\n \n 1 1995 131194\n 2 1996 168270\n 3 1997 195895\n 4 1998 214404\n 5 1999 212258\n 6 2000 213766\n 7 2001 212766\n 8 2002 194972\n 9 2003 267280\n10 2004 384886\n11 2005 472719\n12 2006 468291\n13 2007 465877\n14 2008 462596\n```\n:::\n:::\n\n\n\n## First year of recording by country?\n\n- A lot of countries started recording in about 1995, in fact:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(first_year=min(year)) %>% \n count(first_year)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 14 x 2\n first_year n\n \n 1 1980 2\n 2 1994 2\n 3 1995 130\n 4 1996 31\n 5 1997 17\n 6 1998 6\n 7 1999 10\n 8 2000 4\n 9 2001 1\n10 2002 3\n11 2003 2\n12 2004 2\n13 2005 2\n14 2007 1\n```\n:::\n:::\n\n\n\n- So the reason for the big jump in cases is that so many countries started recording then, not that there really were more cases.\n\n## Some Toronto weather data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/STAC32/toronto_weather.csv\"\nweather <- read_csv(my_url)\nweather\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 24 x 35\n station Year Month element d01 d02 d03 d04 d05 d06 d07\n \n 1 TORONT~ 2018 01 tmax -7.9 -7.1 -5.3 -7.7 -14.7 -15.4 -1 \n 2 TORONT~ 2018 01 tmin -18.6 -12.5 -11.2 -19.7 -20.6 -22.3 -17.5\n 3 TORONT~ 2018 02 tmax 5.6 -8.6 0.4 1.8 -6.6 -3.2 -4.1\n 4 TORONT~ 2018 02 tmin -8.9 -15 -9.7 -8.8 -12 -8.2 -8.7\n 5 TORONT~ 2018 03 tmax NA NA NA NA NA NA 3.1\n 6 TORONT~ 2018 03 tmin NA -0.5 NA -3.1 NA -1.4 0.4\n 7 TORONT~ 2018 04 tmax 4.5 6.5 5 5.7 2.9 5.4 2 \n 8 TORONT~ 2018 04 tmin -2.6 -1.2 2.4 -3.2 -3.9 -2.6 -4.4\n 9 TORONT~ 2018 05 tmax 23.5 26.3 23 24 24.1 17.4 15.9\n10 TORONT~ 2018 05 tmin 8.5 14.4 11.4 9.2 8.5 13.3 10.6\n# i 14 more rows\n# i 24 more variables: d08 , d09 , d10 , d11 ,\n# d12 , d13 , d14 , d15 , d16 , d17 ,\n# d18 , d19 , d20 , d21 , d22 , d23 ,\n# d24 , d25 , d26 , d27 , d28 , d29 ,\n# d30 , d31 \n```\n:::\n:::\n\n\n\n## The columns\n\n- Daily weather records for \"Toronto City\" weather station in 2018:\n\n - `station`: identifier for this weather station (always same here)\n - `Year`, `Month`\n - `element`: whether temperature given was daily max or daily min\n - `d01`, `d02`,... `d31`: day of the month from 1st to 31st.\n\n## Off we go\n\nNumbers in data frame all temperatures (for different days of the month),\nso first step is\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>% \n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 703 x 6\n station Year Month element day temperature\n \n 1 TORONTO CITY 2018 01 tmax d01 -7.9\n 2 TORONTO CITY 2018 01 tmax d02 -7.1\n 3 TORONTO CITY 2018 01 tmax d03 -5.3\n 4 TORONTO CITY 2018 01 tmax d04 -7.7\n 5 TORONTO CITY 2018 01 tmax d05 -14.7\n 6 TORONTO CITY 2018 01 tmax d06 -15.4\n 7 TORONTO CITY 2018 01 tmax d07 -1 \n 8 TORONTO CITY 2018 01 tmax d08 3 \n 9 TORONTO CITY 2018 01 tmax d09 1.6\n10 TORONTO CITY 2018 01 tmax d10 5.9\n# i 693 more rows\n```\n:::\n:::\n\n\n\n## `Element`\n\n- Column `element` contains names of two different variables, that\nshould each be in separate column.\n- Distinct from eg. `m1524` in tuberculosis data, that contained levels of\ntwo different factors, handled by separate.\n- Untangling names of variables handled by `pivot_wider`.\n\n## Handling `element`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE) %>% \n pivot_wider(names_from=element, \n values_from=temperature) \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 355 x 6\n station Year Month day tmax tmin\n \n 1 TORONTO CITY 2018 01 d01 -7.9 -18.6\n 2 TORONTO CITY 2018 01 d02 -7.1 -12.5\n 3 TORONTO CITY 2018 01 d03 -5.3 -11.2\n 4 TORONTO CITY 2018 01 d04 -7.7 -19.7\n 5 TORONTO CITY 2018 01 d05 -14.7 -20.6\n 6 TORONTO CITY 2018 01 d06 -15.4 -22.3\n 7 TORONTO CITY 2018 01 d07 -1 -17.5\n 8 TORONTO CITY 2018 01 d08 3 -1.7\n 9 TORONTO CITY 2018 01 d09 1.6 -0.6\n10 TORONTO CITY 2018 01 d10 5.9 -1.3\n# i 345 more rows\n```\n:::\n:::\n\n\n\n\n\n## Further improvements 1/2\n- We have tidy data now, but can improve things further.\n- `mutate` creates new columns from old (or assign back to change a\nvariable).\n- Would like numerical dates. `separate` works, or pull out number as\nbelow.\n- `select` keeps columns (or drops, with minus). Station name has no\nvalue to us.\n\n\n## Further improvements 2/2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 355 x 6\n Year Month day tmax tmin Day\n \n 1 2018 01 d01 -7.9 -18.6 1\n 2 2018 01 d02 -7.1 -12.5 2\n 3 2018 01 d03 -5.3 -11.2 3\n 4 2018 01 d04 -7.7 -19.7 4\n 5 2018 01 d05 -14.7 -20.6 5\n 6 2018 01 d06 -15.4 -22.3 6\n 7 2018 01 d07 -1 -17.5 7\n 8 2018 01 d08 3 -1.7 8\n 9 2018 01 d09 1.6 -0.6 9\n10 2018 01 d10 5.9 -1.3 10\n# i 345 more rows\n```\n:::\n:::\n\n\n\n\n## Final step(s)\n- Make year-month-day into proper date.\n- Keep only date, tmax, tmin:\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station) %>% \n unite(datestr, c(Year, Month, Day), sep = \"-\") %>%\n mutate(date = as.Date(datestr)) %>%\n select(c(date, tmax, tmin)) -> weather_tidy\n```\n:::\n\n\n\\normalsize\n\n## Our tidy data frame\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 355 x 3\n date tmax tmin\n \n 1 2018-01-01 -7.9 -18.6\n 2 2018-01-02 -7.1 -12.5\n 3 2018-01-03 -5.3 -11.2\n 4 2018-01-04 -7.7 -19.7\n 5 2018-01-05 -14.7 -20.6\n 6 2018-01-06 -15.4 -22.3\n 7 2018-01-07 -1 -17.5\n 8 2018-01-08 3 -1.7\n 9 2018-01-09 1.6 -0.6\n10 2018-01-10 5.9 -1.3\n# i 345 more rows\n```\n:::\n:::\n\n\n\n## Plotting the temperatures\n- Plot temperature against date joined by lines, but with separate lines\nfor max and min. `ggplot` requires something like\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(..., aes(x = date, y = temperature)) + geom_point() + \n geom_line()\n```\n:::\n\n\n\\normalsize\n\nonly we have two temperatures, one a max and one a min, that we\nwant to keep separate.\n\n- The trick: combine `tmax` and `tmin` together into one column, keeping\ntrack of what kind of temp they are. (This actually same format as\nuntidy `weather`.) Are making `weather_tidy`\nuntidy for purposes of drawing graph only.\n- Then can do something like\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(d, aes(x = date, y = temperature, colour = maxmin)) \n + geom_point() + geom_line()\n```\n:::\n\n\n\\normalsize\nto distinguish max and min on graph.\n\n## Setting up plot\n- Since we only need data frame for plot, we can do the\ncolumn-creation and plot in a pipeline.\n- For a `ggplot` in a pipeline, the initial data frame is omitted, because it\nis whatever came out of the previous step.\n- To make those “one column”s: `pivot_longer`. I save the graph to show overleaf:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy %>%\n pivot_longer(tmax:tmin, names_to=\"maxmin\", \n values_to=\"temperature\") %>%\n ggplot(aes(x = date, y = temperature, colour = maxmin)) +\n geom_line() -> g\n```\n:::\n\n\n\n## The plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-beamer/temp-plot-1.pdf)\n:::\n:::\n\n\n\n## Summary of tidying “verbs”\n\n |Verb | Purpose|\n |:-----|:-------------------------------|\n |`pivot_longer` | Combine columns that measure same thing into one |\n |`pivot_wider` | Take column that measures one thing under different conditions and put into multiple columns |\n |`separate` | Turn a column that encodes several variables into several columns |\n |`unite` | Combine several (related) variables into one \"combination\" variable |\n\n `pivot_longer` and `pivot_wider` are opposites; `separate` and\n `unite` are opposites.\n \n\n\n", + "markdown": "---\ntitle: \"Tidying data\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Tidying data {.scrollable}\n\n- Data rarely come to us as we want to use them.\n- Before we can do analysis, typically have organizing to do.\n- This is typical of ANOVA-type data, \"wide format\":\n\n``` \n pig feed1 feed2 feed3 feed4\n 1 60.8 68.7 92.6 87.9\n 2 57.0 67.7 92.1 84.2\n 3 65.0 74.0 90.2 83.1\n 4 58.6 66.3 96.5 85.7\n 5 61.7 69.8 99.1 90.3\n```\n\n- 20 pigs randomly allocated to one of four feeds. At end of study,\n weight of each pig is recorded.\n- Are any differences in mean weights among the feeds?\n- Problem: want all weights in one column, with 2nd column labelling\n which feed. Untidy!\n\n## Tidy and untidy data (Wickham) {.scrollable}\n\n- Data set easier to deal with if:\n - each observation is one row\n - each variable is one column\n - each type of observation unit is one table\n- Data arranged this way called \"tidy\"; otherwise called \"untidy\".\n- For the pig data:\n - response variable is weight, but scattered over 4 columns, which\n are levels of a factor `feed`.\n - Want all the weights in one column, with a second column `feed`\n saying which feed that weight goes with.\n - Then we can run `aov`.\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Reading in the pig data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs1.txt\"\npigs1 <- read_delim(my_url, \" \")\npigs1\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 5 x 5\n pig feed1 feed2 feed3 feed4\n \n1 1 60.8 68.7 92.6 87.9\n2 2 57 67.7 92.1 84.2\n3 3 65 74 90.2 83.1\n4 4 58.6 66.3 96.5 85.7\n5 5 61.7 69.8 99.1 90.3\n```\n:::\n:::\n\n\n\n## Making it longer\n\n- We wanted all the weights in one column, labelled by which feed they\n went with.\n- This is a very common reorganization, and the magic \"verb\" is\n `pivot_longer`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(feed1:feed4, names_to=\"feed\", \n values_to=\"weight\") -> pigs2\npigs2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Alternatives\n\nAny way of choosing the columns to pivot longer is good, eg:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(-pig, names_to=\"feed\", \n values_to=\"weight\") \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\nor\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs1 %>% pivot_longer(starts_with(\"feed\"), names_to=\"feed\", \n values_to=\"weight\") \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 1 feed2 68.7\n 3 1 feed3 92.6\n 4 1 feed4 87.9\n 5 2 feed1 57 \n 6 2 feed2 67.7\n 7 2 feed3 92.1\n 8 2 feed4 84.2\n 9 3 feed1 65 \n10 3 feed2 74 \n11 3 feed3 90.2\n12 3 feed4 83.1\n13 4 feed1 58.6\n14 4 feed2 66.3\n15 4 feed3 96.5\n16 4 feed4 85.7\n17 5 feed1 61.7\n18 5 feed2 69.8\n19 5 feed3 99.1\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Comments\n\n- `pigs2` now in \"long\" format, ready for analysis.\n- Anatomy of `pivot_longer`:\n - columns to combine\n - a name for column that will contain groups (\"names\")\n - a name for column that will contain measurements (\"values\")\n\n## Identifying the pigs\n\n- Values in `pig` identify pigs *within each group*: pig 1 is four\n different pigs!\n- Create unique pig IDs by gluing pig number onto feed:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>% mutate(pig_id=str_c(feed, \"_\", pig)) -> pigs2\npigs2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 4\n pig feed weight pig_id \n \n 1 1 feed1 60.8 feed1_1\n 2 1 feed2 68.7 feed2_1\n 3 1 feed3 92.6 feed3_1\n 4 1 feed4 87.9 feed4_1\n 5 2 feed1 57 feed1_2\n 6 2 feed2 67.7 feed2_2\n 7 2 feed3 92.1 feed3_2\n 8 2 feed4 84.2 feed4_2\n 9 3 feed1 65 feed1_3\n10 3 feed2 74 feed2_3\n11 3 feed3 90.2 feed3_3\n12 3 feed4 83.1 feed4_3\n13 4 feed1 58.6 feed1_4\n14 4 feed2 66.3 feed2_4\n15 4 feed3 96.5 feed3_4\n16 4 feed4 85.7 feed4_4\n17 5 feed1 61.7 feed1_5\n18 5 feed2 69.8 feed2_5\n19 5 feed3 99.1 feed3_5\n20 5 feed4 90.3 feed4_5\n```\n:::\n:::\n\n\n\n## ...and finally, the analysis\n\n- which is just what we saw before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweight.1 <- aov(weight ~ feed, data = pigs2)\nsummary(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.8 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n- The mean weights of pigs on the different feeds are definitely not\n all equal.\n- So we run Tukey to see which ones differ (over).\n\n## Tukey\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(weight.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Tukey multiple comparisons of means\n 95% family-wise confidence level\n\nFit: aov(formula = weight ~ feed, data = pigs2)\n\n$feed\n diff lwr upr p adj\nfeed2-feed1 8.68 3.001038 14.358962 0.0024000\nfeed3-feed1 33.48 27.801038 39.158962 0.0000000\nfeed4-feed1 25.62 19.941038 31.298962 0.0000000\nfeed3-feed2 24.80 19.121038 30.478962 0.0000000\nfeed4-feed2 16.94 11.261038 22.618962 0.0000013\nfeed4-feed3 -7.86 -13.538962 -2.181038 0.0055599\n```\n:::\n:::\n\n\n\nAll of the feeds differ!\n\n## Mean weights by feed\n\nTo find the best and worst, get mean weight by feed group. I borrowed an\nidea from earlier to put the means in descending order:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs2 %>%\n group_by(feed) %>%\n summarize(mean_weight = mean(weight))%>%\n arrange(desc(mean_weight))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n feed mean_weight\n \n1 feed3 94.1\n2 feed4 86.2\n3 feed2 69.3\n4 feed1 60.6\n```\n:::\n:::\n\n\n\nFeed 3 is best, feed 1 worst.\n\n## Should we have any concerns about the ANOVA?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(pigs2, aes(x = feed, y = weight)) + geom_boxplot()\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-beamer/pigfeedplot-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Feed 2 has an outlier\n- But there are only 5 pigs in each group\n- The conclusion is so clear that I am OK with this.\n\n## Tuberculosis\n\n- The World Health Organization keeps track of number of cases of\n various diseases, eg. tuberculosis.\n- Some data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/tb.csv\"\ntb <- read_csv(my_url)\n```\n:::\n\n\n\n## The data (randomly chosen rows)\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% slice_sample(n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 22\n iso2 year m04 m514 m014 m1524 m2534 m3544 m4554 m5564 m65\n \n 1 SG 2003 NA NA 1 17 28 68 96 80 133\n 2 CA 1997 NA NA 0 21 55 44 30 44 90\n 3 LK 1997 NA NA 11 215 390 596 623 396 271\n 4 VN 1993 NA NA NA NA NA NA NA NA NA\n 5 CV 1996 NA NA NA NA NA NA NA NA NA\n 6 MK 2006 0 0 0 15 15 25 37 18 7\n 7 SD 2003 NA NA 489 1195 1644 1271 856 645 473\n 8 VE 1995 NA NA NA NA NA NA NA NA NA\n 9 MH 2003 NA NA 6 4 2 7 7 2 2\n10 MG 1986 NA NA NA NA NA NA NA NA NA\n# i 11 more variables: mu , f04 , f514 , f014 ,\n# f1524 , f2534 , f3544 , f4554 , f5564 ,\n# f65 , fu \n```\n:::\n:::\n\n\n\nMany rows:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnrow(tb)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 5769\n```\n:::\n:::\n\n\n\n## What we have\n\n- Variables: country (abbreviated), year. Then number of cases for\n each gender and age group, eg. `m1524` is males aged 15--24. Also\n `mu` and `fu`, where age is unknown.\n- Lots of missings. Want to get rid of.\n- Abbreviations\n [here](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>% \n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) -> tb2\n```\n:::\n\n\n\n- Code for `pivot_longer`:\n - columns to make longer\n - column to contain the names (categorical)\n - column to contain the values (quantitative)\n - drop missings in the values\n\n## Results (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 4\n iso2 year genage freq\n \n 1 AD 1996 m014 0\n 2 AD 1996 m1524 0\n 3 AD 1996 m2534 0\n 4 AD 1996 m3544 4\n 5 AD 1996 m4554 1\n 6 AD 1996 m5564 0\n 7 AD 1996 m65 0\n 8 AD 1996 f014 0\n 9 AD 1996 f1524 1\n10 AD 1996 f2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n\n\n## Separating\n\n- 4 columns, but 5 variables, since `genage` contains both gender and\n age group. Split that up using separate.\n- `separate` needs 3 things:\n - what to separate (no quotes needed),\n - what to separate into (here you do need quotes),\n - how to split.\n- For \"how to split\", here \"after first character\":\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb2 %>% separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\") -> tb3\ntb3\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 5\n iso2 year gender age freq\n \n 1 AD 1996 m 014 0\n 2 AD 1996 m 1524 0\n 3 AD 1996 m 2534 0\n 4 AD 1996 m 3544 4\n 5 AD 1996 m 4554 1\n 6 AD 1996 m 5564 0\n 7 AD 1996 m 65 0\n 8 AD 1996 f 014 0\n 9 AD 1996 f 1524 1\n10 AD 1996 f 2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n\n\n## Tidied tuberculosis data (some)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 5\n iso2 year gender age freq\n \n 1 AD 1996 m 014 0\n 2 AD 1996 m 1524 0\n 3 AD 1996 m 2534 0\n 4 AD 1996 m 3544 4\n 5 AD 1996 m 4554 1\n 6 AD 1996 m 5564 0\n 7 AD 1996 m 65 0\n 8 AD 1996 f 014 0\n 9 AD 1996 f 1524 1\n10 AD 1996 f 2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n::: {.cell}\n\n:::\n\n\n\n## In practice... {.smaller}\n\n- instead of doing the pipe one step at a time, you *debug* it one\n step at a time, and when you have each step working, you use that\n step's output as input to the next step, thus:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb %>%\n pivot_longer(m04:fu, names_to = \"genage\", \n values_to = \"freq\", values_drop_na = TRUE) %>% \n separate_wider_position(genage, \n widths = c(\"gender\" = 1, \"age\" = 4), \n too_few = \"align_start\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 35,750 x 5\n iso2 year gender age freq\n \n 1 AD 1996 m 014 0\n 2 AD 1996 m 1524 0\n 3 AD 1996 m 2534 0\n 4 AD 1996 m 3544 4\n 5 AD 1996 m 4554 1\n 6 AD 1996 m 5564 0\n 7 AD 1996 m 65 0\n 8 AD 1996 f 014 0\n 9 AD 1996 f 1524 1\n10 AD 1996 f 2534 1\n# i 35,740 more rows\n```\n:::\n:::\n\n\n\n- You can split the R code over as many lines as you like, as long as\n each line is incomplete, so that R knows more is to come.\n- I like to put the pipe symbol on the end of the line.\n\n## Total tuberculosis cases by year (some of the years)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>%\n filter(between(year, 1991, 1998)) %>% \n group_by(year) %>% summarize(total=sum(freq))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n year total\n \n1 1991 544\n2 1992 512\n3 1993 492\n4 1994 750\n5 1995 513971\n6 1996 635705\n7 1997 733204\n8 1998 840389\n```\n:::\n:::\n\n\n\n- Something very interesting happened between 1994 and 1995.\n\n## To find out what\n\n- try counting up total cases by country:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(total=sum(freq)) %>% \n arrange(desc(total))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 213 x 2\n iso2 total\n \n 1 CN 4065174\n 2 IN 3966169\n 3 ID 1129015\n 4 ZA 900349\n 5 BD 758008\n 6 VN 709695\n 7 CD 603095\n 8 PH 490040\n 9 BR 440609\n10 KE 431523\n# i 203 more rows\n```\n:::\n:::\n\n\n\n## What years do I have for China?\n\nChina started recording in 1995, which is at least part of the problem:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% filter(iso2==\"CN\") %>% \n group_by(year) %>% \n summarize(total=sum(freq))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 14 x 2\n year total\n \n 1 1995 131194\n 2 1996 168270\n 3 1997 195895\n 4 1998 214404\n 5 1999 212258\n 6 2000 213766\n 7 2001 212766\n 8 2002 194972\n 9 2003 267280\n10 2004 384886\n11 2005 472719\n12 2006 468291\n13 2007 465877\n14 2008 462596\n```\n:::\n:::\n\n\n\n## First year of recording by country?\n\n- A lot of countries started recording in about 1995, in fact:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\ntb3 %>% group_by(iso2) %>% \n summarize(first_year=min(year)) %>% \n count(first_year)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 14 x 2\n first_year n\n \n 1 1980 2\n 2 1994 2\n 3 1995 130\n 4 1996 31\n 5 1997 17\n 6 1998 6\n 7 1999 10\n 8 2000 4\n 9 2001 1\n10 2002 3\n11 2003 2\n12 2004 2\n13 2005 2\n14 2007 1\n```\n:::\n:::\n\n\n\n- So the reason for the big jump in cases is that so many countries\n started recording then, not that there really were more cases.\n\n## Some Toronto weather data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/STAC32/toronto_weather.csv\"\nweather <- read_csv(my_url)\nweather\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 24 x 35\n station Year Month element d01 d02 d03 d04 d05 d06 d07\n \n 1 TORONT~ 2018 01 tmax -7.9 -7.1 -5.3 -7.7 -14.7 -15.4 -1 \n 2 TORONT~ 2018 01 tmin -18.6 -12.5 -11.2 -19.7 -20.6 -22.3 -17.5\n 3 TORONT~ 2018 02 tmax 5.6 -8.6 0.4 1.8 -6.6 -3.2 -4.1\n 4 TORONT~ 2018 02 tmin -8.9 -15 -9.7 -8.8 -12 -8.2 -8.7\n 5 TORONT~ 2018 03 tmax NA NA NA NA NA NA 3.1\n 6 TORONT~ 2018 03 tmin NA -0.5 NA -3.1 NA -1.4 0.4\n 7 TORONT~ 2018 04 tmax 4.5 6.5 5 5.7 2.9 5.4 2 \n 8 TORONT~ 2018 04 tmin -2.6 -1.2 2.4 -3.2 -3.9 -2.6 -4.4\n 9 TORONT~ 2018 05 tmax 23.5 26.3 23 24 24.1 17.4 15.9\n10 TORONT~ 2018 05 tmin 8.5 14.4 11.4 9.2 8.5 13.3 10.6\n# i 14 more rows\n# i 24 more variables: d08 , d09 , d10 , d11 ,\n# d12 , d13 , d14 , d15 , d16 , d17 ,\n# d18 , d19 , d20 , d21 , d22 , d23 ,\n# d24 , d25 , d26 , d27 , d28 , d29 ,\n# d30 , d31 \n```\n:::\n:::\n\n\n\n## The columns\n\n- Daily weather records for \"Toronto City\" weather station in 2018:\n\n - `station`: identifier for this weather station (always same\n here)\n - `Year`, `Month`\n - `element`: whether temperature given was daily max or daily min\n - `d01`, `d02`,... `d31`: day of the month from 1st to 31st.\n\n## Off we go\n\nNumbers in data frame all temperatures (for different days of the\nmonth), so first step is\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>% \n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 703 x 6\n station Year Month element day temperature\n \n 1 TORONTO CITY 2018 01 tmax d01 -7.9\n 2 TORONTO CITY 2018 01 tmax d02 -7.1\n 3 TORONTO CITY 2018 01 tmax d03 -5.3\n 4 TORONTO CITY 2018 01 tmax d04 -7.7\n 5 TORONTO CITY 2018 01 tmax d05 -14.7\n 6 TORONTO CITY 2018 01 tmax d06 -15.4\n 7 TORONTO CITY 2018 01 tmax d07 -1 \n 8 TORONTO CITY 2018 01 tmax d08 3 \n 9 TORONTO CITY 2018 01 tmax d09 1.6\n10 TORONTO CITY 2018 01 tmax d10 5.9\n# i 693 more rows\n```\n:::\n:::\n\n\n\n## `Element`\n\n- Column `element` contains names of two different variables, that\n should each be in separate column.\n- Distinct from eg. `m1524` in tuberculosis data, that contained\n levels of two different factors, handled by separate.\n- Untangling names of variables handled by `pivot_wider`.\n\n## Handling `element`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", \n values_drop_na = TRUE) %>% \n pivot_wider(names_from=element, \n values_from=temperature) \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 355 x 6\n station Year Month day tmax tmin\n \n 1 TORONTO CITY 2018 01 d01 -7.9 -18.6\n 2 TORONTO CITY 2018 01 d02 -7.1 -12.5\n 3 TORONTO CITY 2018 01 d03 -5.3 -11.2\n 4 TORONTO CITY 2018 01 d04 -7.7 -19.7\n 5 TORONTO CITY 2018 01 d05 -14.7 -20.6\n 6 TORONTO CITY 2018 01 d06 -15.4 -22.3\n 7 TORONTO CITY 2018 01 d07 -1 -17.5\n 8 TORONTO CITY 2018 01 d08 3 -1.7\n 9 TORONTO CITY 2018 01 d09 1.6 -0.6\n10 TORONTO CITY 2018 01 d10 5.9 -1.3\n# i 345 more rows\n```\n:::\n:::\n\n\n\n## Further improvements 1/2\n\n- We have tidy data now, but can improve things further.\n- `mutate` creates new columns from old (or assign back to change a\n variable).\n- Would like numerical dates. `separate` works, or pull out number as\n below.\n- `select` keeps columns (or drops, with minus). Station name has no\n value to us.\n\n## Further improvements 2/2\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 355 x 6\n Year Month day tmax tmin Day\n \n 1 2018 01 d01 -7.9 -18.6 1\n 2 2018 01 d02 -7.1 -12.5 2\n 3 2018 01 d03 -5.3 -11.2 3\n 4 2018 01 d04 -7.7 -19.7 4\n 5 2018 01 d05 -14.7 -20.6 5\n 6 2018 01 d06 -15.4 -22.3 6\n 7 2018 01 d07 -1 -17.5 7\n 8 2018 01 d08 3 -1.7 8\n 9 2018 01 d09 1.6 -0.6 9\n10 2018 01 d10 5.9 -1.3 10\n# i 345 more rows\n```\n:::\n:::\n\n\n\n## Final step(s)\n\n- Make year-month-day into proper date.\n- Keep only date, tmax, tmin:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather %>%\n pivot_longer(d01:d31, names_to=\"day\", \n values_to=\"temperature\", values_drop_na = T) %>% \n pivot_wider(names_from=element, values_from=temperature) %>% \n mutate(Day = parse_number(day)) %>%\n select(-station) %>% \n unite(datestr, c(Year, Month, Day), sep = \"-\") %>%\n mutate(date = as.Date(datestr)) %>%\n select(c(date, tmax, tmin)) -> weather_tidy\n```\n:::\n\n\n\n## Our tidy data frame\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 355 x 3\n date tmax tmin\n \n 1 2018-01-01 -7.9 -18.6\n 2 2018-01-02 -7.1 -12.5\n 3 2018-01-03 -5.3 -11.2\n 4 2018-01-04 -7.7 -19.7\n 5 2018-01-05 -14.7 -20.6\n 6 2018-01-06 -15.4 -22.3\n 7 2018-01-07 -1 -17.5\n 8 2018-01-08 3 -1.7\n 9 2018-01-09 1.6 -0.6\n10 2018-01-10 5.9 -1.3\n# i 345 more rows\n```\n:::\n:::\n\n\n\n## Plotting the temperatures\n\n- Plot temperature against date joined by lines, but with separate\n lines for max and min. `ggplot` requires something like\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(..., aes(x = date, y = temperature)) + geom_point() + \n geom_line()\n```\n:::\n\n\n\n\\normalsize\n\nonly we have two temperatures, one a max and one a min, that we want to\nkeep separate.\n\n- The trick: combine `tmax` and `tmin` together into one column,\n keeping track of what kind of temp they are. (This actually same\n format as untidy `weather`.) Are making `weather_tidy` untidy for\n purposes of drawing graph only.\n- Then can do something like\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(d, aes(x = date, y = temperature, colour = maxmin)) \n + geom_point() + geom_line()\n```\n:::\n\n\n\n\\normalsize\n\nto distinguish max and min on graph.\n\n## Setting up plot\n\n- Since we only need data frame for plot, we can do the\n column-creation and plot in a pipeline.\n- For a `ggplot` in a pipeline, the initial data frame is omitted,\n because it is whatever came out of the previous step.\n- To make those \"one column\"s: `pivot_longer`. I save the graph to\n show overleaf:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nweather_tidy %>%\n pivot_longer(tmax:tmin, names_to=\"maxmin\", \n values_to=\"temperature\") %>%\n ggplot(aes(x = date, y = temperature, colour = maxmin)) + geom_point() +\n geom_line() -> g\n```\n:::\n\n\n\n## The plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](tidying_files/figure-beamer/temp-plot-1.pdf)\n:::\n:::\n\n\n\n## Summary of tidying \"verbs\"\n\n| Verb | Purpose |\n|:---------------|:---------------------------------------------------------------------------------------------|\n| `pivot_longer` | Combine columns that measure same thing into one |\n| `pivot_wider` | Take column that measures one thing under different conditions and put into multiple columns |\n| `separate` | Turn a column that encodes several variables into several columns |\n| `unite` | Combine several (related) variables into one \"combination\" variable |\n\n`pivot_longer` and `pivot_wider` are opposites; `separate` and `unite`\nare opposites.\n", "supporting": [ "tidying_files/figure-beamer" ], diff --git a/_freeze/tidying/figure-beamer/pigfeedplot-1.pdf b/_freeze/tidying/figure-beamer/pigfeedplot-1.pdf index 3f91823..8c923d7 100644 Binary files a/_freeze/tidying/figure-beamer/pigfeedplot-1.pdf and b/_freeze/tidying/figure-beamer/pigfeedplot-1.pdf differ diff --git a/_freeze/tidying/figure-beamer/temp-plot-1.pdf b/_freeze/tidying/figure-beamer/temp-plot-1.pdf index e0df321..c1989e8 100644 Binary files a/_freeze/tidying/figure-beamer/temp-plot-1.pdf and b/_freeze/tidying/figure-beamer/temp-plot-1.pdf differ diff --git a/_freeze/tidying/figure-revealjs/temp-plot-1.png b/_freeze/tidying/figure-revealjs/temp-plot-1.png index 1c14eea..2685a37 100644 Binary files a/_freeze/tidying/figure-revealjs/temp-plot-1.png and b/_freeze/tidying/figure-revealjs/temp-plot-1.png differ diff --git a/_freeze/tidying/figure-revealjs/tidying-R-36-1.png b/_freeze/tidying/figure-revealjs/tidying-R-36-1.png new file mode 100644 index 0000000..2685a37 Binary files /dev/null and b/_freeze/tidying/figure-revealjs/tidying-R-36-1.png differ diff --git a/_freeze/wider_wrong/execute-results/html.json b/_freeze/wider_wrong/execute-results/html.json index 529ef3a..ded0f41 100644 --- a/_freeze/wider_wrong/execute-results/html.json +++ b/_freeze/wider_wrong/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "2717f5d4b398fd2c3a62fdcb9ee192ea", + "hash": "34990131dc9a577df7f24ffd0dc55b1d", "result": { - "markdown": "---\ntitle: \"When pivot-wider goes wrong\"\n---\n\n\n## Packages\n\nThe inevitable:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Some long data that should be wide\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Six observations of variable `y`, but three measured before some treatment and three measured after. \n- Really matched pairs, so want column of `y`-values for `pre` and for `post`.\n- `pivot_wider`.\n\n## What happens here?\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Should be *three* `pre` values and *three* `post`. Why did this happen?\n- `pivot_wider` needs to know which *row* to put each observation in.\n- Uses combo of columns *not* named in `pivot_wider`, here `obs` (only).\n\n## The problem\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- There are 6 different `obs` values, so 6 different rows.\n- No data for `obs` 2 and `pre`, so that cell missing (`NA`).\n- Not enough data (6 obs) to fill 12 ($= 2 \\times 6$) cells.\n- `obs` needs to say which subject provided which *2* observations.\n\n## Fixing it up\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- column `subject` shows which subject provided each `pre` and `post`. \n- when we do `pivot_wider`, now only *3* rows, one per subject.\n\n## Coming out right\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- row each observation goes to determined by other column `subject`, and now a `pre` and `post` for each `subject`.\n- right layout for matched pairs $t$ or to make differences for sign test or normal quantile plot.\n- \"spaghetti plot\" needs data longer, as `d2`.\n\n## Spaghetti plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% mutate(time = fct_inorder(time)) %>% \n ggplot(aes(x = time, y = y, group = subject)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](wider_wrong_files/figure-revealjs/wider-wrong-6-1.png){width=960}\n:::\n:::\n\n\n- each subject's `y` decreases over time, with subject 1 highest overall.\n\n## Another example\n\n- Two independent samples this time\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n- These should be arranged like this\n- but what if we make them wider?\n\n## Wider\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- row determined by what not used for `pivot_wider`: nothing!\n- everything smooshed into *one* row!\n- this time, too *much* data for the layout.\n- Four data values squeezed into each of the two cells: \"list-columns\".\n\n## Get the data out\n\n- To expand list-columns out into the data values they contain, can use `unnest`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y) %>% \n unnest(c(control, treatment))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n- in this case, wrong layout, because data values not paired.\n\n## A proper use of list-columns\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% nest_by(group) %>% \n summarize(n = nrow(data), \n mean_y = mean(data$y), \n sd_y = sd(data$y))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- another way to do `group_by` and `summarize` to find stats by group.\n- run this one piece at a time to see what it does.\n\n", + "markdown": "---\ntitle: \"When pivot-wider goes wrong\"\n---\n\n\n## Packages\n\nThe inevitable:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## Some long data that should be wide\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Six observations of variable `y`, but three measured before some\n treatment and three measured after.\n- Really matched pairs, so want column of `y`-values for `pre` and for\n `post`.\n- `pivot_wider`.\n\n## What happens here?\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Should be *three* `pre` values and *three* `post`. Why did this\n happen?\n- `pivot_wider` needs to know which *row* to put each observation in.\n- Uses combo of columns *not* named in `pivot_wider`, here `obs`\n (only).\n\n## The problem\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- There are 6 different `obs` values, so 6 different rows.\n- No data for `obs` 2 and `pre`, so that cell missing (`NA`).\n- Not enough data (6 obs) to fill 12 ($= 2 \\times 6$) cells.\n- `obs` needs to say which subject provided which *2* observations.\n\n## Fixing it up\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- column `subject` shows which subject provided each `pre` and `post`.\n- when we do `pivot_wider`, now only *3* rows, one per subject.\n\n## Coming out right\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- row each observation goes to determined by other column `subject`,\n and now a `pre` and `post` for each `subject`.\n- right layout for matched pairs $t$ or to make differences for sign\n test or normal quantile plot.\n- \"spaghetti plot\" needs data longer, as `d2`.\n\n## Spaghetti plot\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% mutate(time = fct_inorder(time)) %>% \n ggplot(aes(x = time, y = y, group = subject)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](wider_wrong_files/figure-revealjs/wider-wrong-6-1.png){width=960}\n:::\n:::\n\n\n- each subject's `y` decreases over time, with subject 1 highest\n overall.\n\n## Another example\n\n- Two independent samples this time\n\n\n::: {.cell}\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- These should be arranged like this\n- but what if we make them wider?\n\n## Wider\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- row determined by what not used for `pivot_wider`: nothing!\n- everything smooshed into *one* row!\n- this time, too *much* data for the layout.\n- Four data values squeezed into each of the two cells:\n \"list-columns\".\n\n## Get the data out\n\n- To expand list-columns out into the data values they contain, can\n use `unnest`:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y) %>% \n unnest(c(control, treatment))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- in this case, wrong layout, because data values not paired.\n\n## A proper use of list-columns\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% nest_by(group) %>% \n summarize(n = nrow(data), \n mean_y = mean(data$y), \n sd_y = sd(data$y))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- another way to do `group_by` and `summarize` to find stats by group.\n- run this one piece at a time to see what it does.\n", "supporting": [ "wider_wrong_files/figure-revealjs" ], diff --git a/_freeze/wider_wrong/execute-results/tex.json b/_freeze/wider_wrong/execute-results/tex.json index 5aaaa02..64dbde2 100644 --- a/_freeze/wider_wrong/execute-results/tex.json +++ b/_freeze/wider_wrong/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "2717f5d4b398fd2c3a62fdcb9ee192ea", + "hash": "34990131dc9a577df7f24ffd0dc55b1d", "result": { - "markdown": "---\ntitle: \"When pivot-wider goes wrong\"\n---\n\n\n\n## Packages\n\nThe inevitable:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n\n## Some long data that should be wide\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n obs time y\n \n1 1 pre 19\n2 2 post 18\n3 3 pre 17\n4 4 post 16\n5 5 pre 15\n6 6 post 14\n```\n:::\n:::\n\n\n\n- Six observations of variable `y`, but three measured before some treatment and three measured after. \n- Really matched pairs, so want column of `y`-values for `pre` and for `post`.\n- `pivot_wider`.\n\n## What happens here?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n obs pre post\n \n1 1 19 NA\n2 2 NA 18\n3 3 17 NA\n4 4 NA 16\n5 5 15 NA\n6 6 NA 14\n```\n:::\n:::\n\n\n\n- Should be *three* `pre` values and *three* `post`. Why did this happen?\n- `pivot_wider` needs to know which *row* to put each observation in.\n- Uses combo of columns *not* named in `pivot_wider`, here `obs` (only).\n\n## The problem\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n obs pre post\n \n1 1 19 NA\n2 2 NA 18\n3 3 17 NA\n4 4 NA 16\n5 5 15 NA\n6 6 NA 14\n```\n:::\n:::\n\n\n\n- There are 6 different `obs` values, so 6 different rows.\n- No data for `obs` 2 and `pre`, so that cell missing (`NA`).\n- Not enough data (6 obs) to fill 12 ($= 2 \\times 6$) cells.\n- `obs` needs to say which subject provided which *2* observations.\n\n## Fixing it up\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n subject time y\n \n1 1 pre 19\n2 1 post 18\n3 2 pre 17\n4 2 post 16\n5 3 pre 15\n6 3 post 14\n```\n:::\n:::\n\n\n\n- column `subject` shows which subject provided each `pre` and `post`. \n- when we do `pivot_wider`, now only *3* rows, one per subject.\n\n## Coming out right\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 3\n subject pre post\n \n1 1 19 18\n2 2 17 16\n3 3 15 14\n```\n:::\n:::\n\n\n\n- row each observation goes to determined by other column `subject`, and now a `pre` and `post` for each `subject`.\n- right layout for matched pairs $t$ or to make differences for sign test or normal quantile plot.\n- \"spaghetti plot\" needs data longer, as `d2`.\n\n## Spaghetti plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% mutate(time = fct_inorder(time)) %>% \n ggplot(aes(x = time, y = y, group = subject)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](wider_wrong_files/figure-beamer/wider-wrong-6-1.pdf)\n:::\n:::\n\n\n\n- each subject's `y` decreases over time, with subject 1 highest overall.\n\n## Another example\n\n- Two independent samples this time\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n group y\n \n1 control 8\n2 control 11\n3 control 13\n4 control 14\n5 treatment 12\n6 treatment 15\n7 treatment 16\n8 treatment 17\n```\n:::\n:::\n\n\n- These should be arranged like this\n- but what if we make them wider?\n\n## Wider\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 2\n control treatment\n \n1 \n```\n:::\n:::\n\n\n\n- row determined by what not used for `pivot_wider`: nothing!\n- everything smooshed into *one* row!\n- this time, too *much* data for the layout.\n- Four data values squeezed into each of the two cells: \"list-columns\".\n\n## Get the data out\n\n- To expand list-columns out into the data values they contain, can use `unnest`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y) %>% \n unnest(c(control, treatment))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n control treatment\n \n1 8 12\n2 11 15\n3 13 16\n4 14 17\n```\n:::\n:::\n\n\n- in this case, wrong layout, because data values not paired.\n\n## A proper use of list-columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% nest_by(group) %>% \n summarize(n = nrow(data), \n mean_y = mean(data$y), \n sd_y = sd(data$y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 4\n# Groups: group [2]\n group n mean_y sd_y\n \n1 control 4 11.5 2.65\n2 treatment 4 15 2.16\n```\n:::\n:::\n\n\n\n- another way to do `group_by` and `summarize` to find stats by group.\n- run this one piece at a time to see what it does.\n\n", + "markdown": "---\ntitle: \"When pivot-wider goes wrong\"\n---\n\n\n\n## Packages\n\nThe inevitable:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n## Some long data that should be wide\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n obs time y\n \n1 1 pre 19\n2 2 post 18\n3 3 pre 17\n4 4 post 16\n5 5 pre 15\n6 6 post 14\n```\n:::\n:::\n\n\n\n- Six observations of variable `y`, but three measured before some\n treatment and three measured after.\n- Really matched pairs, so want column of `y`-values for `pre` and for\n `post`.\n- `pivot_wider`.\n\n## What happens here?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n obs pre post\n \n1 1 19 NA\n2 2 NA 18\n3 3 17 NA\n4 4 NA 16\n5 5 15 NA\n6 6 NA 14\n```\n:::\n:::\n\n\n\n- Should be *three* `pre` values and *three* `post`. Why did this\n happen?\n- `pivot_wider` needs to know which *row* to put each observation in.\n- Uses combo of columns *not* named in `pivot_wider`, here `obs`\n (only).\n\n## The problem\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n obs pre post\n \n1 1 19 NA\n2 2 NA 18\n3 3 17 NA\n4 4 NA 16\n5 5 15 NA\n6 6 NA 14\n```\n:::\n:::\n\n\n\n- There are 6 different `obs` values, so 6 different rows.\n- No data for `obs` 2 and `pre`, so that cell missing (`NA`).\n- Not enough data (6 obs) to fill 12 ($= 2 \\times 6$) cells.\n- `obs` needs to say which subject provided which *2* observations.\n\n## Fixing it up\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 3\n subject time y\n \n1 1 pre 19\n2 1 post 18\n3 2 pre 17\n4 2 post 16\n5 3 pre 15\n6 3 post 14\n```\n:::\n:::\n\n\n\n- column `subject` shows which subject provided each `pre` and `post`.\n- when we do `pivot_wider`, now only *3* rows, one per subject.\n\n## Coming out right\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% pivot_wider(names_from = time, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 3\n subject pre post\n \n1 1 19 18\n2 2 17 16\n3 3 15 14\n```\n:::\n:::\n\n\n\n- row each observation goes to determined by other column `subject`,\n and now a `pre` and `post` for each `subject`.\n- right layout for matched pairs $t$ or to make differences for sign\n test or normal quantile plot.\n- \"spaghetti plot\" needs data longer, as `d2`.\n\n## Spaghetti plot\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd2 %>% mutate(time = fct_inorder(time)) %>% \n ggplot(aes(x = time, y = y, group = subject)) +\n geom_point() + geom_line()\n```\n\n::: {.cell-output-display}\n![](wider_wrong_files/figure-beamer/wider-wrong-6-1.pdf)\n:::\n:::\n\n\n\n- each subject's `y` decreases over time, with subject 1 highest\n overall.\n\n## Another example\n\n- Two independent samples this time\n\n\n\n::: {.cell}\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 2\n group y\n \n1 control 8\n2 control 11\n3 control 13\n4 control 14\n5 treatment 12\n6 treatment 15\n7 treatment 16\n8 treatment 17\n```\n:::\n:::\n\n\n\n- These should be arranged like this\n- but what if we make them wider?\n\n## Wider\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 2\n control treatment\n \n1 \n```\n:::\n:::\n\n\n\n- row determined by what not used for `pivot_wider`: nothing!\n- everything smooshed into *one* row!\n- this time, too *much* data for the layout.\n- Four data values squeezed into each of the two cells:\n \"list-columns\".\n\n## Get the data out\n\n- To expand list-columns out into the data values they contain, can\n use `unnest`:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% pivot_wider(names_from = group, values_from = y) %>% \n unnest(c(control, treatment))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 2\n control treatment\n \n1 8 12\n2 11 15\n3 13 16\n4 14 17\n```\n:::\n:::\n\n\n\n- in this case, wrong layout, because data values not paired.\n\n## A proper use of list-columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nd3 %>% nest_by(group) %>% \n summarize(n = nrow(data), \n mean_y = mean(data$y), \n sd_y = sd(data$y))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 4\n# Groups: group [2]\n group n mean_y sd_y\n \n1 control 4 11.5 2.65\n2 treatment 4 15 2.16\n```\n:::\n:::\n\n\n\n- another way to do `group_by` and `summarize` to find stats by group.\n- run this one piece at a time to see what it does.\n", "supporting": [ "wider_wrong_files/figure-beamer" ], diff --git a/_freeze/wider_wrong/figure-beamer/wider-wrong-6-1.pdf b/_freeze/wider_wrong/figure-beamer/wider-wrong-6-1.pdf index 8ffe0b2..5a5b76b 100644 Binary files a/_freeze/wider_wrong/figure-beamer/wider-wrong-6-1.pdf and b/_freeze/wider_wrong/figure-beamer/wider-wrong-6-1.pdf differ diff --git a/_freeze/windmill/execute-results/html.json b/_freeze/windmill/execute-results/html.json index 8db123c..2d7cc74 100644 --- a/_freeze/windmill/execute-results/html.json +++ b/_freeze/windmill/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "9cf91f30620e00aed9d2b32994720aa9", + "hash": "92b83cecbd2e1ea7c16d3ad02c616937", "result": { - "markdown": "---\ntitle: \"Case study: windmill\"\n---\n\n\n\n## The windmill data\n- Engineer: does amount of electricity generated by windmill depend on\nhow strongly wind blowing?\n- Measurements of wind speed and DC current generated at various\ntimes.\n- Assume the “various times” to be randomly selected — aim to\ngeneralize to “this windmill at all times”.\n- Research questions:\n - Relationship between wind speed and current generated?\n - If so, what kind of relationship?\n - Can we model relationship to do predictions?\n \n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n## Reading in the data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/windmill.csv\"\nwindmill <- read_csv(my_url)\nwindmill\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Strategy\n- Two quantitative variables, looking for relationship: regression\nmethods.\n- Start with picture (scatterplot).\n- Fit models and do model checking, fixing up things as necessary.\n- Scatterplot:\n - 2 variables, `DC_output` and `wind_velocity`.\n - First is output/response, other is input/explanatory.\n - Put `DC_output` on vertical scale.\n- Add trend, but don’t want to assume linear:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(se = F) \n```\n:::\n\n\n## Scatterplot\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-5-1.png){width=960}\n:::\n:::\n\n\n## Comments\n- Definitely a relationship: as wind velocity increases, so does DC\noutput. (As you’d expect.)\n- Is relationship linear? To help judge, `geom_smooth` smooths\nscatterplot trend. (Trend called “loess”, “Locally weighted least\nsquares” which downweights outliers. Not constrained to be straight.)\n- Trend more or less linear for while, then curves downwards (levelling off?). Straight\nline not so good here.\n\n## Fit a straight line (and see what happens)\n\n\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.1 <- lm(DC_output ~ wind_velocity, data = windmill)\nsummary(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity, data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.59869 -0.14099 0.06059 0.17262 0.32184 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.13088 0.12599 1.039 0.31 \nwind_velocity 0.24115 0.01905 12.659 7.55e-12 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.2361 on 23 degrees of freedom\nMultiple R-squared: 0.8745,\tAdjusted R-squared: 0.869 \nF-statistic: 160.3 on 1 and 23 DF, p-value: 7.546e-12\n```\n:::\n:::\n\n\\normalsize\n\n\n\n## Another way of looking at the output\n\n- The standard output tends to go off the bottom of the page rather easily. Package `broom` has these:\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nshowing that the R-squared is 87%, and\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\nshowing the intercept and slope and their significance.\n\n## Comments\n- Strategy: `lm` actually fits the regression. Store results in a variable.\nThen look at the results, eg. via `summary` or `glance`/`tidy`. \n- My strategy for model names: base on response variable (or data frame name) and a number.\nAllows me to fit several models to same data and keep track of which\nis which.\n- Results actually pretty good: `wind.velocity` strongly significant,\nR-squared (87%) high.\n- How to check whether regression is appropriate? Look at the\nresiduals, observed minus predicted, plotted against fitted (predicted).\n- Plot using the regression object as “data frame” (in a couple of slides).\n\n## Scatterplot, but with line\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method=\"lm\", se = FALSE)\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-11-1.png){width=960}\n:::\n:::\n\n\n\n## Plot of residuals against fitted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.1, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-13-1.png){width=960}\n:::\n:::\n\n\n## Comments on residual plot\n- Residual plot should be a random scatter of points.\n- Should be no pattern “left over” after fitting the regression.\n- Smooth trend should be more or less straight across at 0.\n- Here, have a curved trend on residual plot.\n- This means original relationship must have been a curve (as we saw\non original scatterplot).\n- Possible ways to fit a curve:\n - Add a squared term in explanatory variable.\n - Transform response variable (doesn’t work well here).\n - See what science tells you about mathematical form of relationship,\nand try to apply.\n\n## Parabolas and fitting parabola model\n- A parabola has equation\n$$y = ax^2 + bx + c$$\nwith coefficients $a, b, c$. About the simplest function that is not a straight\nline.\n- Fit one using `lm` by adding $x^2$ to right side of model formula with +:\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.2 <- lm(DC_output ~ wind_velocity + I(wind_velocity^2),\n data = windmill\n)\n```\n:::\n\n\n- The `I()` necessary because `^` in model formula otherwise means\nsomething different (to do with interactions in ANOVA).\n- Call it *parabola model*.\n\n## Parabola model output\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\scriptsize\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Comments on output\n\n\n- R-squared has gone up a lot, from 87% (line) to 97% (parabola).\n- Coefficient of squared term strongly significant (P-value $6.59 \\times 10^{−8}$).\n- Adding squared term has definitely improved fit of model.\n- Parabola model better than linear one.\n- But...need to check residuals again.\n\n## Residual plot from parabola model\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.2, aes(y = .resid, x = .fitted)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-18-1.png){width=960}\n:::\n:::\n\n\n## Make scatterplot with fitted line and curve \n\n- Residual plot basically random. Good.\n- Scatterplot with fitted line and curve like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method = \"lm\", se = F) +\n geom_line(data = DC.2, aes(y = .fitted))\n```\n:::\n\n\n## Comments\n\n- This plots: \n - scatterplot (`geom_point`); \n - straight line (via tweak to\n`geom_smooth`, which draws best-fitting line); \n - fitted curve, using the\npredicted `DC_output` values, joined by lines (with points not shown).\n- Trick in the `geom_line` is use the predictions as the `y`-points to join\nby lines (from `DC.2`), instead of the original data points. Without the\n`data` and `aes` in the `geom_line`, original data points would be joined\nby lines.\n\n## Scatterplot with fitted line and curve\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-19-1.png){width=960}\n:::\n:::\n\n\nCurve clearly fits better than line. \n\n\n## Another approach to a curve\n- There is a problem with parabolas, which we’ll see later.\n- Ask engineer, “what should happen\nas wind velocity increases?”:\n\n - Upper limit on electricity generated, but otherwise, the larger the\n wind velocity, the more electricity generated.\n\n- Mathematically, *asymptote*. Straight lines and parabolas\ndon’t have them, but eg. $y = 1/x$ does: as $x$ gets bigger, $y$\napproaches zero without reaching it.\n- What happens to $y = a + b(1/x)$ as $x$ gets large?\n - $y$ gets closer and closer to $a$: that is, $a$ is asymptote.\n- Fit this, call it asymptote model.\n- Fitting the model here because we have math to justify it.\n - Alternative, $y = a + be^{−x}$ , approaches asymptote faster.\n\n## How to fit asymptote model?\n- Define new explanatory variable to be $1/x$,\nand predict $y$ from it.\n- $x$ is velocity, distance over time.\n- So $1/x$ is time over distance. In walking world, if you walk 5 km/h,\ntake 12 minutes to walk 1 km, called your pace. So 1 over\n`wind_velocity` we call `wind_pace`.\n- Make a scatterplot first to check for straightness (next page).\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwindmill %>% mutate(wind_pace = 1 / wind_velocity) -> windmill\nggplot(windmill, aes(y = DC_output, x = wind_pace)) +\n geom_point() + geom_smooth(se = F)\n```\n:::\n\n\n- and run regression like this (output page after):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.3 <- lm(DC_output ~ wind_pace, data = windmill)\n```\n:::\n\n\n## Scatterplot for wind_pace\n\nPretty straight. Blue actually smooth curve not line:\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-20-1.png){width=960}\n:::\n:::\n\n\n\n\n\n## Regression output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n- R-squared, 98%, even higher than for parabola model (97%).\n- Simpler model, only one explanatory variable (`wind.pace`) vs. 2 for\nparabola model (`wind.velocity` and its square).\n- `wind.pace` (unsurprisingly) strongly significant.\n- Looks good, but check residual plot (over). \n\n\n## Residual plot for asymptote model\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.3, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/resida-1.png){width=960}\n:::\n:::\n\n\n\n## Plotting trends on scatterplot\n- Residual plot not bad. But residuals go up to 0.10 and down to\n−0.20, suggesting possible skewness (not normal). I think it’s not\nperfect, but OK overall.\n- Next: plot scatterplot with all three fitted lines/curves on it (for\ncomparison), with legend saying which is which.\n- First make data frame containing what we need, taken from the right\nplaces:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 <- tibble(\n wind_velocity = windmill$wind_velocity,\n DC_output = windmill$DC_output,\n linear = fitted(DC.1),\n parabola = fitted(DC.2),\n asymptote = fitted(DC.3)\n)\n```\n:::\n\n\n## What’s in `w2`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making the plot\n- `ggplot` likes to have one column of $x$’s to plot, and one column of\n$y$’s, with another column for distinguishing things.\n- But we have three columns of fitted values, that need to be combined\ninto one.\n- `pivot_longer`, then plot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 %>%\n pivot_longer(linear:asymptote, names_to=\"model\", \n values_to=\"fit\") %>%\n ggplot(aes(x = wind_velocity, y = DC_output)) +\n geom_point() +\n geom_line(aes(y = fit, colour = model)) \n```\n:::\n\n\n## Scatterplot with fitted curves\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-25-1.png){width=960}\n:::\n:::\n\n\n## Comments\n- Predictions from curves are very similar.\n- Predictions from asymptote model as good, and from simpler model\n(one $x$ not two), so prefer those.\n- Go back to asymptote model summary.\n\n## Asymptote model summary\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n- Intercept in this model about 3.\n- Intercept of asymptote model is the asymptote (upper limit of\n`DC.output`).\n- Not close to asymptote yet.\n- Therefore, from this model, wind could get stronger and would\ngenerate appreciably more electricity.\n- This is extrapolation! Would like more data from times when\n`wind.velocity` higher.\n- Slope −7. Why negative?\n - As wind.velocity increases, wind.pace goes down, and DC.output goes up. Check.\n- Actual slope number hard to interpret.\n\n## Checking back in with research questions\n- Is there a relationship between wind speed and current generated?\n - Yes.\n- If so, what kind of relationship is it?\n - One with an asymptote.\n- Can we model the relationship, in such a way that we can do\npredictions?\n - Yes, see model DC.3 and plot of fitted curve.\n- Good. Job done.\n\n## Job done, kinda\n- Just because the parabola model and asymptote model agree over the\nrange of the data, doesn’t necessarily mean they agree everywhere.\n- Extend range of wind.velocity to 1 to 16 (steps of 0.5), and\npredict DC.output according to the two models:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nwv <- seq(1, 16, 0.5)\nwv\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0\n[14] 7.5 8.0 8.5 9.0 9.5 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5\n[27] 14.0 14.5 15.0 15.5 16.0\n```\n:::\n:::\n\n\n- R has `predict`, which requires what to predict for, as data frame.\nThe data frame has to contain values, with matching names, for all\nexplanatory variables in regression(s).\n\n## Setting up data frame to predict from\n\n- Linear model had just `wind_velocity`.\n- Parabola model had that as well (squared one will be calculated)\n- Asymptote model had just `wind_pace` (reciprocal of velocity).\n- So create data frame called `wv_new` with those in:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new <- tibble(wind_velocity = wv, wind_pace = 1 / wv)\n```\n:::\n\n\n## `wv_new`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Doing predictions, one for each model\n- Use same names as before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinear <- predict(DC.1, wv_new)\nparabola <- predict(DC.2, wv_new)\nasymptote <- predict(DC.3, wv_new)\n```\n:::\n\n- Put it all into a data frame for plotting, along with original data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits <- tibble(\n wind_velocity = wv_new$wind_velocity,\n linear, parabola, asymptote\n)\n```\n:::\n\n\n## `my_fits`\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\n## Making a plot 1/2\n\n- To make a plot, we use the same trick as last time to get all three\npredictions on a plot with a legend (saving result to add to later):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits %>%\n pivot_longer(\n linear:asymptote,\n names_to=\"model\", \n values_to=\"fit\"\n ) %>%\n ggplot(aes(\n y = fit, x = wind_velocity,\n colour = model\n )) + geom_line() -> g\n```\n:::\n\n\n## Making a plot 2/2\n\n- The observed wind velocities were in this range:\n\n::: {.cell}\n\n```{.r .cell-code}\n(vels <- range(windmill$wind_velocity))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.45 10.20\n```\n:::\n:::\n\n\n- `DC.output` between 0 and 3 from asymptote model. Add rectangle to\ngraph around where the data were:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng + geom_rect(\n xmin = vels[1], xmax = vels[2], ymin = 0, ymax = 3,\n alpha=0, colour = \"black\"\n)\n```\n:::\n\n\n## The plot\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-35-1.png){width=960}\n:::\n:::\n\n\n## Comments (1)\n- Over range of data, two models agree with each other well.\n- Outside range of data, they disagree violently!\n- For larger `wind.velocity`, asymptote model behaves reasonably,\nparabola model does not.\n- What happens as `wind.velocity` goes to zero? Should find\n`DC.output` goes to zero as well. Does it?\n\n## Comments (2)\n\n- For parabola model:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Nope, goes to −1.16 (intercept), actually significantly different from\nzero.\n\n## Comments (3): asymptote model\n\n\\small\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n- As `wind.velocity` heads to 0, wind.pace heads to $+\\infty$, so\nDC.output heads to $−\\infty$! \n- Also need more data for small `wind.velocity` to understand\nrelationship. (Is there a lower asymptote?)\n- Best we can do now is to predict `DC.output` to be zero for small\n`wind.velocity`.\n- Assumes a “threshold” wind velocity below which no electricity\ngenerated at all.\n\n## Summary\n- Often, in data analysis, there is no completely satisfactory conclusion,\nas here.\n- Have to settle for model that works OK, with restrictions.\n- Always something else you can try.\n- At some point you have to say “I stop.”\n\n\n\n", + "markdown": "---\ntitle: \"Case study: windmill\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## The windmill data\n\n- Engineer: does amount of electricity generated by windmill depend on\n how strongly wind blowing?\n- Measurements of wind speed and DC current generated at various\n times.\n- Assume the \"various times\" to be randomly selected --- aim to\n generalize to \"this windmill at all times\".\n- Research questions:\n - Relationship between wind speed and current generated?\n - If so, what kind of relationship?\n - Can we model relationship to do predictions?\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n## Reading in the data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/windmill.csv\"\nwindmill <- read_csv(my_url)\nwindmill\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Strategy\n\n- Two quantitative variables, looking for relationship: regression\n methods.\n- Start with picture (scatterplot).\n- Fit models and do model checking, fixing up things as necessary.\n- Scatterplot:\n - 2 variables, `DC_output` and `wind_velocity`.\n - First is output/response, other is input/explanatory.\n - Put `DC_output` on vertical scale.\n- Add trend, but don't want to assume linear:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth() \n```\n:::\n\n\n## Scatterplot\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-5-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Definitely a relationship: as wind velocity increases, so does DC\n output. (As you'd expect.)\n- Is relationship linear? To help judge, `geom_smooth` smooths\n scatterplot trend. (Trend called \"loess\", \"Locally weighted least\n squares\" which downweights outliers. Not constrained to be\n straight.)\n- Trend more or less linear for while, then curves downwards\n (levelling off?). Straight line not so good here.\n\n## Fit a straight line (and see what happens)\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.1 <- lm(DC_output ~ wind_velocity, data = windmill)\nsummary(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity, data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.59869 -0.14099 0.06059 0.17262 0.32184 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.13088 0.12599 1.039 0.31 \nwind_velocity 0.24115 0.01905 12.659 7.55e-12 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.2361 on 23 degrees of freedom\nMultiple R-squared: 0.8745,\tAdjusted R-squared: 0.869 \nF-statistic: 160.3 on 1 and 23 DF, p-value: 7.546e-12\n```\n:::\n:::\n\n\n## Another way of looking at the output\n\n- The standard output tends to go off the bottom of the page rather\n easily. Package `broom` has these:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\nshowing that the R-squared is 87%, and\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.1)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\nshowing the intercept and slope and their significance.\n\n## Comments\n\n- Strategy: `lm` actually fits the regression. Store results in a\n variable. Then look at the results, eg. via `summary` or\n `glance`/`tidy`.\n- My strategy for model names: base on response variable (or data\n frame name) and a number. Allows me to fit several models to same\n data and keep track of which is which.\n- Results actually pretty good: `wind.velocity` strongly significant,\n R-squared (87%) high.\n- How to check whether regression is appropriate? Look at the\n residuals, observed minus predicted, plotted against fitted\n (predicted).\n- Plot using the regression object as \"data frame\" (in a couple of\n slides).\n\n## Scatterplot, but with line\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method=\"lm\", se = FALSE)\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-11-1.png){width=960}\n:::\n:::\n\n\n## Plot of residuals against fitted values\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.1, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-13-1.png){width=960}\n:::\n:::\n\n\n## Comments on residual plot\n\n- Residual plot should be a random scatter of points.\n- Should be no pattern \"left over\" after fitting the regression.\n- Smooth trend should be more or less straight across at 0.\n- Here, have a curved trend on residual plot.\n- This means original relationship must have been a curve (as we saw\n on original scatterplot).\n- Possible ways to fit a curve:\n - Add a squared term in explanatory variable.\n - Transform response variable (doesn't work well here).\n - See what science tells you about mathematical form of\n relationship, and try to apply.\n\n## normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/unnamed-chunk-1-1.png){width=960}\n:::\n:::\n\n\n## Parabolas and fitting parabola model\n\n- A parabola has equation $$y = ax^2 + bx + c$$ with coefficients\n $a, b, c$. About the simplest function that is not a straight line.\n- Fit one using `lm` by adding $x^2$ to right side of model formula\n with +:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.2 <- lm(DC_output ~ wind_velocity + I(wind_velocity^2),\n data = windmill\n)\n```\n:::\n\n\n- The `I()` necessary because `^` in model formula otherwise means\n something different (to do with interactions in ANOVA).\n- Call it *parabola model*.\n\n## Parabola model output\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity + I(wind_velocity^2), \n data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.26347 -0.02537 0.01264 0.03908 0.19903 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.155898 0.174650 -6.618 1.18e-06 ***\nwind_velocity 0.722936 0.061425 11.769 5.77e-11 ***\nI(wind_velocity^2) -0.038121 0.004797 -7.947 6.59e-08 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.1227 on 22 degrees of freedom\nMultiple R-squared: 0.9676,\tAdjusted R-squared: 0.9646 \nF-statistic: 328.3 on 2 and 22 DF, p-value: < 2.2e-16\n```\n:::\n\n```{.r .cell-code}\n# tidy(DC.2)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity + I(wind_velocity^2), \n data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.26347 -0.02537 0.01264 0.03908 0.19903 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.155898 0.174650 -6.618 1.18e-06 ***\nwind_velocity 0.722936 0.061425 11.769 5.77e-11 ***\nI(wind_velocity^2) -0.038121 0.004797 -7.947 6.59e-08 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.1227 on 22 degrees of freedom\nMultiple R-squared: 0.9676,\tAdjusted R-squared: 0.9646 \nF-statistic: 328.3 on 2 and 22 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n## Comments on output\n\n- R-squared has gone up a lot, from 87% (line) to 97% (parabola).\n- Coefficient of squared term strongly significant (P-value\n $6.59 \\times 10^{−8}$).\n- Adding squared term has definitely improved fit of model.\n- Parabola model better than linear one.\n- But...need to check residuals again.\n\n## Residual plot from parabola model\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.2, aes(y = .resid, x = .fitted)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-18-1.png){width=960}\n:::\n:::\n\n\n## normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.2, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/unnamed-chunk-3-1.png){width=960}\n:::\n:::\n\n\nThis distribution has long tails, which should worry us at least some.\n\n## Make scatterplot with fitted line and curve\n\n- Residual plot basically random. Good.\n- Scatterplot with fitted line and curve like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method = \"lm\", se = F) +\n geom_line(data = DC.2, aes(y = .fitted))\n```\n:::\n\n\n## Comments\n\n- This plots:\n - scatterplot (`geom_point`);\n - straight line (via tweak to `geom_smooth`, which draws\n best-fitting line);\n - fitted curve, using the predicted `DC_output` values, joined by\n lines (with points not shown).\n- Trick in the `geom_line` is use the predictions as the `y`-points to\n join by lines (from `DC.2`), instead of the original data points.\n Without the `data` and `aes` in the `geom_line`, original data\n points would be joined by lines.\n\n## Scatterplot with fitted line and curve\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-19-1.png){width=960}\n:::\n:::\n\n\nCurve clearly fits better than line.\n\n## Another approach to a curve\n\n- There is a problem with parabolas, which we'll see later.\n\n- Ask engineer, \"what should happen as wind velocity increases?\":\n\n - Upper limit on electricity generated, but otherwise, the larger\n the wind velocity, the more electricity generated.\n\n- Mathematically, *asymptote*. Straight lines and parabolas don't have\n them, but eg. $y = 1/x$ does: as $x$ gets bigger, $y$ approaches\n zero without reaching it.\n\n- What happens to $y = a + b(1/x)$ as $x$ gets large?\n\n - $y$ gets closer and closer to $a$: that is, $a$ is asymptote.\n\n- Fit this, call it asymptote model.\n\n- Fitting the model here because we have math to justify it.\n\n - Alternative, $y = a + be^{−x}$ , approaches asymptote faster.\n\n## How to fit asymptote model?\n\n- Define new explanatory variable to be $1/x$, and predict $y$ from\n it.\n- $x$ is velocity, distance over time.\n- So $1/x$ is time over distance. In walking world, if you walk 5\n km/h, take 12 minutes to walk 1 km, called your pace. So 1 over\n `wind_velocity` we call `wind_pace`.\n- Make a scatterplot first to check for straightness (next page).\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwindmill %>% mutate(wind_pace = 1 / wind_velocity) -> windmill\nggplot(windmill, aes(y = DC_output, x = wind_pace)) +\n geom_point() + geom_smooth(se = F)\n```\n:::\n\n\n- and run regression like this (output page after):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.3 <- lm(DC_output ~ wind_pace, data = windmill)\nsummary(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_pace, data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.20547 -0.04940 0.01100 0.08352 0.12204 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 2.9789 0.0449 66.34 <2e-16 ***\nwind_pace -6.9345 0.2064 -33.59 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.09417 on 23 degrees of freedom\nMultiple R-squared: 0.98,\tAdjusted R-squared: 0.9792 \nF-statistic: 1128 on 1 and 23 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n## Scatterplot for wind_pace\n\nPretty straight. Blue actually smooth curve not line:\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-20-1.png){width=960}\n:::\n:::\n\n\n## Regression output\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- R-squared, 98%, even higher than for parabola model (97%).\n- Simpler model, only one explanatory variable (`wind.pace`) vs. 2 for\n parabola model (`wind.velocity` and its square).\n- `wind.pace` (unsurprisingly) strongly significant.\n- Looks good, but check residual plot (over).\n\n## Residual plot for asymptote model\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.3, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/resida-1.png){width=960}\n:::\n:::\n\n\n## normal quantile plot of residuals\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/unnamed-chunk-4-1.png){width=960}\n:::\n:::\n\n\nThis is skewed (left), but is not bad (and definitely better than the\none for the parabola model).\n\n## Plotting trends on scatterplot\n\n- Residual plot not bad. But residuals go up to 0.10 and down to\n −0.20, suggesting possible skewness (not normal). I think it's not\n perfect, but OK overall.\n- Next: plot scatterplot with all three fitted lines/curves on it (for\n comparison), with legend saying which is which.\n- First make data frame containing what we need, taken from the right\n places:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 <- tibble(\n wind_velocity = windmill$wind_velocity,\n DC_output = windmill$DC_output,\n linear = fitted(DC.1),\n parabola = fitted(DC.2),\n asymptote = fitted(DC.3)\n)\n```\n:::\n\n\n## What's in `w2`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making the plot\n\n- `ggplot` likes to have one column of $x$'s to plot, and one column\n of $y$'s, with another column for distinguishing things.\n- But we have three columns of fitted values, that need to be combined\n into one.\n- `pivot_longer`, then plot:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 %>%\n pivot_longer(linear:asymptote, names_to=\"model\", \n values_to=\"fit\") %>%\n ggplot(aes(x = wind_velocity, y = DC_output)) +\n geom_point() +\n geom_line(aes(y = fit, colour = model)) \n```\n:::\n\n\n## Scatterplot with fitted curves\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-25-1.png){width=960}\n:::\n:::\n\n\n## Comments\n\n- Predictions from curves are very similar.\n- Predictions from asymptote model as good, and from simpler model\n (one $x$ not two), so prefer those.\n- Go back to asymptote model summary.\n\n## Asymptote model summary\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Comments\n\n- Intercept in this model about 3.\n- Intercept of asymptote model is the asymptote (upper limit of\n `DC.output`).\n- Not close to asymptote yet.\n- Therefore, from this model, wind could get stronger and would\n generate appreciably more electricity.\n- This is extrapolation! Would like more data from times when\n `wind.velocity` higher.\n- Slope −7. Why negative?\n - As wind.velocity increases, wind.pace goes down, and DC.output\n goes up. Check.\n- Actual slope number hard to interpret.\n\n## Checking back in with research questions\n\n- Is there a relationship between wind speed and current generated?\n - Yes.\n- If so, what kind of relationship is it?\n - One with an asymptote.\n- Can we model the relationship, in such a way that we can do\n predictions?\n - Yes, see model DC.3 and plot of fitted curve.\n- Good. Job done.\n\n## Job done, kinda\n\n- Just because the parabola model and asymptote model agree over the\n range of the data, doesn't necessarily mean they agree everywhere.\n- Extend range of wind.velocity to 1 to 16 (steps of 0.5), and predict\n DC.output according to the two models:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nwv <- seq(1, 16, 0.5)\nwv\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0\n[14] 7.5 8.0 8.5 9.0 9.5 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5\n[27] 14.0 14.5 15.0 15.5 16.0\n```\n:::\n:::\n\n\n- R has `predict`, which requires what to predict for, as data frame.\n The data frame has to contain values, with matching names, for all\n explanatory variables in regression(s).\n\n## Setting up data frame to predict from\n\n- Linear model had just `wind_velocity`.\n- Parabola model had that as well (squared one will be calculated)\n- Asymptote model had just `wind_pace` (reciprocal of velocity).\n- So create data frame called `wv_new` with those in:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new <- tibble(wind_velocity = wv, wind_pace = 1 / wv)\n```\n:::\n\n\n## `wv_new`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Doing predictions, one for each model\n\n- Use same names as before:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinear <- predict(DC.1, wv_new)\nparabola <- predict(DC.2, wv_new)\nasymptote <- predict(DC.3, wv_new)\n```\n:::\n\n\n- Put it all into a data frame for plotting, along with original data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits <- tibble(\n wind_velocity = wv_new$wind_velocity,\n linear, parabola, asymptote\n)\n```\n:::\n\n\n## `my_fits`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Making a plot 1/2\n\n- To make a plot, we use the same trick as last time to get all three\n predictions on a plot with a legend (saving result to add to later):\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits %>%\n pivot_longer(\n linear:asymptote,\n names_to=\"model\", \n values_to=\"fit\"\n ) %>%\n ggplot(aes(\n y = fit, x = wind_velocity,\n colour = model\n )) + geom_line() -> g\n```\n:::\n\n\n## Making a plot 2/2\n\n- The observed wind velocities were in this range:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(vels <- range(windmill$wind_velocity))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.45 10.20\n```\n:::\n:::\n\n\n- `DC.output` between 0 and 3 from asymptote model. Add rectangle to\n graph around where the data were:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng + geom_rect(\n xmin = vels[1], xmax = vels[2], ymin = 0, ymax = 3,\n alpha=0, colour = \"black\"\n)\n```\n:::\n\n\n## The plot\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-revealjs/windmill-35-1.png){width=960}\n:::\n:::\n\n\n## Comments (1)\n\n- Over range of data, two models agree with each other well.\n- Outside range of data, they disagree violently!\n- For larger `wind.velocity`, asymptote model behaves reasonably,\n parabola model does not.\n- What happens as `wind.velocity` goes to zero? Should find\n `DC.output` goes to zero as well. Does it?\n\n## Comments (2)\n\n- For parabola model:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n- Nope, goes to −1.16 (intercept), actually significantly different\n from zero.\n\n## Comments (3): asymptote model\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n- As `wind.velocity` heads to 0, wind.pace heads to $+\\infty$, so\n DC.output heads to $−\\infty$!\n- Also need more data for small `wind.velocity` to understand\n relationship. (Is there a lower asymptote?)\n- Best we can do now is to predict `DC.output` to be zero for small\n `wind.velocity`.\n- Assumes a \"threshold\" wind velocity below which no electricity\n generated at all.\n\n## Summary\n\n- Often, in data analysis, there is no completely satisfactory\n conclusion, as here.\n- Have to settle for model that works OK, with restrictions.\n- Always something else you can try.\n- At some point you have to say \"I stop.\"\n", "supporting": [ "windmill_files/figure-revealjs" ], diff --git a/_freeze/windmill/execute-results/tex.json b/_freeze/windmill/execute-results/tex.json index 2e0f727..90fce97 100644 --- a/_freeze/windmill/execute-results/tex.json +++ b/_freeze/windmill/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "9cf91f30620e00aed9d2b32994720aa9", + "hash": "92b83cecbd2e1ea7c16d3ad02c616937", "result": { - "markdown": "---\ntitle: \"Case study: windmill\"\n---\n\n\n\n\n## The windmill data\n- Engineer: does amount of electricity generated by windmill depend on\nhow strongly wind blowing?\n- Measurements of wind speed and DC current generated at various\ntimes.\n- Assume the “various times” to be randomly selected — aim to\ngeneralize to “this windmill at all times”.\n- Research questions:\n - Relationship between wind speed and current generated?\n - If so, what kind of relationship?\n - Can we model relationship to do predictions?\n \n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n\n## Reading in the data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/windmill.csv\"\nwindmill <- read_csv(my_url)\nwindmill\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 25 x 2\n wind_velocity DC_output\n \n 1 5 1.58 \n 2 6 1.82 \n 3 3.4 1.06 \n 4 2.7 0.5 \n 5 10 2.24 \n 6 9.7 2.39 \n 7 9.55 2.29 \n 8 3.05 0.558\n 9 8.15 2.17 \n10 6.2 1.87 \n# i 15 more rows\n```\n:::\n:::\n\n\n\n## Strategy\n- Two quantitative variables, looking for relationship: regression\nmethods.\n- Start with picture (scatterplot).\n- Fit models and do model checking, fixing up things as necessary.\n- Scatterplot:\n - 2 variables, `DC_output` and `wind_velocity`.\n - First is output/response, other is input/explanatory.\n - Put `DC_output` on vertical scale.\n- Add trend, but don’t want to assume linear:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(se = F) \n```\n:::\n\n\n\n## Scatterplot\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-5-1.pdf)\n:::\n:::\n\n\n\n## Comments\n- Definitely a relationship: as wind velocity increases, so does DC\noutput. (As you’d expect.)\n- Is relationship linear? To help judge, `geom_smooth` smooths\nscatterplot trend. (Trend called “loess”, “Locally weighted least\nsquares” which downweights outliers. Not constrained to be straight.)\n- Trend more or less linear for while, then curves downwards (levelling off?). Straight\nline not so good here.\n\n## Fit a straight line (and see what happens)\n\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.1 <- lm(DC_output ~ wind_velocity, data = windmill)\nsummary(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity, data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.59869 -0.14099 0.06059 0.17262 0.32184 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.13088 0.12599 1.039 0.31 \nwind_velocity 0.24115 0.01905 12.659 7.55e-12 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.2361 on 23 degrees of freedom\nMultiple R-squared: 0.8745,\tAdjusted R-squared: 0.869 \nF-statistic: 160.3 on 1 and 23 DF, p-value: 7.546e-12\n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n## Another way of looking at the output\n\n- The standard output tends to go off the bottom of the page rather easily. Package `broom` has these:\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.874 0.869 0.236 160. 7.55e-12 1 1.66 2.68 6.33\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\\normalsize\n\nshowing that the R-squared is 87%, and\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 0.131 0.126 1.04 3.10e- 1\n2 wind_velocity 0.241 0.0190 12.7 7.55e-12\n```\n:::\n:::\n\n\n\\normalsize\n\nshowing the intercept and slope and their significance.\n\n## Comments\n- Strategy: `lm` actually fits the regression. Store results in a variable.\nThen look at the results, eg. via `summary` or `glance`/`tidy`. \n- My strategy for model names: base on response variable (or data frame name) and a number.\nAllows me to fit several models to same data and keep track of which\nis which.\n- Results actually pretty good: `wind.velocity` strongly significant,\nR-squared (87%) high.\n- How to check whether regression is appropriate? Look at the\nresiduals, observed minus predicted, plotted against fitted (predicted).\n- Plot using the regression object as “data frame” (in a couple of slides).\n\n## Scatterplot, but with line\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method=\"lm\", se = FALSE)\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-11-1.pdf)\n:::\n:::\n\n\n\n\n## Plot of residuals against fitted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.1, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-13-1.pdf)\n:::\n:::\n\n\n\n## Comments on residual plot\n- Residual plot should be a random scatter of points.\n- Should be no pattern “left over” after fitting the regression.\n- Smooth trend should be more or less straight across at 0.\n- Here, have a curved trend on residual plot.\n- This means original relationship must have been a curve (as we saw\non original scatterplot).\n- Possible ways to fit a curve:\n - Add a squared term in explanatory variable.\n - Transform response variable (doesn’t work well here).\n - See what science tells you about mathematical form of relationship,\nand try to apply.\n\n## Parabolas and fitting parabola model\n- A parabola has equation\n$$y = ax^2 + bx + c$$\nwith coefficients $a, b, c$. About the simplest function that is not a straight\nline.\n- Fit one using `lm` by adding $x^2$ to right side of model formula with +:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.2 <- lm(DC_output ~ wind_velocity + I(wind_velocity^2),\n data = windmill\n)\n```\n:::\n\n\n\n- The `I()` necessary because `^` in model formula otherwise means\nsomething different (to do with interactions in ANOVA).\n- Call it *parabola model*.\n\n## Parabola model output\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.16 0.175 -6.62 1.18e- 6\n2 wind_velocity 0.723 0.0614 11.8 5.77e-11\n3 I(wind_velocity^2) -0.0381 0.00480 -7.95 6.59e- 8\n```\n:::\n:::\n\n\n\n\\scriptsize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.968 0.965 0.123 328. 4.16e-17 2 18.6 -29.2 -24.3\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\\normalsize\n\n## Comments on output\n\n\n- R-squared has gone up a lot, from 87% (line) to 97% (parabola).\n- Coefficient of squared term strongly significant (P-value $6.59 \\times 10^{−8}$).\n- Adding squared term has definitely improved fit of model.\n- Parabola model better than linear one.\n- But...need to check residuals again.\n\n## Residual plot from parabola model\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.2, aes(y = .resid, x = .fitted)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-18-1.pdf)\n:::\n:::\n\n\n\n## Make scatterplot with fitted line and curve \n\n- Residual plot basically random. Good.\n- Scatterplot with fitted line and curve like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method = \"lm\", se = F) +\n geom_line(data = DC.2, aes(y = .fitted))\n```\n:::\n\n\n\n## Comments\n\n- This plots: \n - scatterplot (`geom_point`); \n - straight line (via tweak to\n`geom_smooth`, which draws best-fitting line); \n - fitted curve, using the\npredicted `DC_output` values, joined by lines (with points not shown).\n- Trick in the `geom_line` is use the predictions as the `y`-points to join\nby lines (from `DC.2`), instead of the original data points. Without the\n`data` and `aes` in the `geom_line`, original data points would be joined\nby lines.\n\n## Scatterplot with fitted line and curve\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-19-1.pdf)\n:::\n:::\n\n\n\nCurve clearly fits better than line. \n\n\n## Another approach to a curve\n- There is a problem with parabolas, which we’ll see later.\n- Ask engineer, “what should happen\nas wind velocity increases?”:\n\n - Upper limit on electricity generated, but otherwise, the larger the\n wind velocity, the more electricity generated.\n\n- Mathematically, *asymptote*. Straight lines and parabolas\ndon’t have them, but eg. $y = 1/x$ does: as $x$ gets bigger, $y$\napproaches zero without reaching it.\n- What happens to $y = a + b(1/x)$ as $x$ gets large?\n - $y$ gets closer and closer to $a$: that is, $a$ is asymptote.\n- Fit this, call it asymptote model.\n- Fitting the model here because we have math to justify it.\n - Alternative, $y = a + be^{−x}$ , approaches asymptote faster.\n\n## How to fit asymptote model?\n- Define new explanatory variable to be $1/x$,\nand predict $y$ from it.\n- $x$ is velocity, distance over time.\n- So $1/x$ is time over distance. In walking world, if you walk 5 km/h,\ntake 12 minutes to walk 1 km, called your pace. So 1 over\n`wind_velocity` we call `wind_pace`.\n- Make a scatterplot first to check for straightness (next page).\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwindmill %>% mutate(wind_pace = 1 / wind_velocity) -> windmill\nggplot(windmill, aes(y = DC_output, x = wind_pace)) +\n geom_point() + geom_smooth(se = F)\n```\n:::\n\n\n\n- and run regression like this (output page after):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.3 <- lm(DC_output ~ wind_pace, data = windmill)\n```\n:::\n\n\n\n## Scatterplot for wind_pace\n\nPretty straight. Blue actually smooth curve not line:\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-20-1.pdf)\n:::\n:::\n\n\n\n\n\n\n## Regression output\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.980 0.979 0.0942 1128. 4.74e-21 1 24.6 -43.3 -39.6\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 2.98 0.0449 66.3 8.92e-28\n2 wind_pace -6.93 0.206 -33.6 4.74e-21\n```\n:::\n:::\n\n\n\n## Comments\n- R-squared, 98%, even higher than for parabola model (97%).\n- Simpler model, only one explanatory variable (`wind.pace`) vs. 2 for\nparabola model (`wind.velocity` and its square).\n- `wind.pace` (unsurprisingly) strongly significant.\n- Looks good, but check residual plot (over). \n\n\n## Residual plot for asymptote model\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.3, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/resida-1.pdf)\n:::\n:::\n\n\n\n\n## Plotting trends on scatterplot\n- Residual plot not bad. But residuals go up to 0.10 and down to\n−0.20, suggesting possible skewness (not normal). I think it’s not\nperfect, but OK overall.\n- Next: plot scatterplot with all three fitted lines/curves on it (for\ncomparison), with legend saying which is which.\n- First make data frame containing what we need, taken from the right\nplaces:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 <- tibble(\n wind_velocity = windmill$wind_velocity,\n DC_output = windmill$DC_output,\n linear = fitted(DC.1),\n parabola = fitted(DC.2),\n asymptote = fitted(DC.3)\n)\n```\n:::\n\n\n\n## What’s in `w2`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 25 x 5\n wind_velocity DC_output linear parabola asymptote\n \n 1 5 1.58 1.34 1.51 1.59 \n 2 6 1.82 1.58 1.81 1.82 \n 3 3.4 1.06 0.951 0.861 0.939\n 4 2.7 0.5 0.782 0.518 0.411\n 5 10 2.24 2.54 2.26 2.29 \n 6 9.7 2.39 2.47 2.27 2.26 \n 7 9.55 2.29 2.43 2.27 2.25 \n 8 3.05 0.558 0.866 0.694 0.705\n 9 8.15 2.17 2.10 2.20 2.13 \n10 6.2 1.87 1.63 1.86 1.86 \n# i 15 more rows\n```\n:::\n:::\n\n\n\n## Making the plot\n- `ggplot` likes to have one column of $x$’s to plot, and one column of\n$y$’s, with another column for distinguishing things.\n- But we have three columns of fitted values, that need to be combined\ninto one.\n- `pivot_longer`, then plot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 %>%\n pivot_longer(linear:asymptote, names_to=\"model\", \n values_to=\"fit\") %>%\n ggplot(aes(x = wind_velocity, y = DC_output)) +\n geom_point() +\n geom_line(aes(y = fit, colour = model)) \n```\n:::\n\n\n\n## Scatterplot with fitted curves\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-25-1.pdf)\n:::\n:::\n\n\n\n## Comments\n- Predictions from curves are very similar.\n- Predictions from asymptote model as good, and from simpler model\n(one $x$ not two), so prefer those.\n- Go back to asymptote model summary.\n\n## Asymptote model summary\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 2.98 0.0449 66.3 8.92e-28\n2 wind_pace -6.93 0.206 -33.6 4.74e-21\n```\n:::\n:::\n\n\n\n## Comments\n- Intercept in this model about 3.\n- Intercept of asymptote model is the asymptote (upper limit of\n`DC.output`).\n- Not close to asymptote yet.\n- Therefore, from this model, wind could get stronger and would\ngenerate appreciably more electricity.\n- This is extrapolation! Would like more data from times when\n`wind.velocity` higher.\n- Slope −7. Why negative?\n - As wind.velocity increases, wind.pace goes down, and DC.output goes up. Check.\n- Actual slope number hard to interpret.\n\n## Checking back in with research questions\n- Is there a relationship between wind speed and current generated?\n - Yes.\n- If so, what kind of relationship is it?\n - One with an asymptote.\n- Can we model the relationship, in such a way that we can do\npredictions?\n - Yes, see model DC.3 and plot of fitted curve.\n- Good. Job done.\n\n## Job done, kinda\n- Just because the parabola model and asymptote model agree over the\nrange of the data, doesn’t necessarily mean they agree everywhere.\n- Extend range of wind.velocity to 1 to 16 (steps of 0.5), and\npredict DC.output according to the two models:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nwv <- seq(1, 16, 0.5)\nwv\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0\n[14] 7.5 8.0 8.5 9.0 9.5 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5\n[27] 14.0 14.5 15.0 15.5 16.0\n```\n:::\n:::\n\n\n\n- R has `predict`, which requires what to predict for, as data frame.\nThe data frame has to contain values, with matching names, for all\nexplanatory variables in regression(s).\n\n## Setting up data frame to predict from\n\n- Linear model had just `wind_velocity`.\n- Parabola model had that as well (squared one will be calculated)\n- Asymptote model had just `wind_pace` (reciprocal of velocity).\n- So create data frame called `wv_new` with those in:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new <- tibble(wind_velocity = wv, wind_pace = 1 / wv)\n```\n:::\n\n\n\n## `wv_new`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 31 x 2\n wind_velocity wind_pace\n \n 1 1 1 \n 2 1.5 0.667\n 3 2 0.5 \n 4 2.5 0.4 \n 5 3 0.333\n 6 3.5 0.286\n 7 4 0.25 \n 8 4.5 0.222\n 9 5 0.2 \n10 5.5 0.182\n# i 21 more rows\n```\n:::\n:::\n\n\n\n## Doing predictions, one for each model\n- Use same names as before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinear <- predict(DC.1, wv_new)\nparabola <- predict(DC.2, wv_new)\nasymptote <- predict(DC.3, wv_new)\n```\n:::\n\n\n- Put it all into a data frame for plotting, along with original data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits <- tibble(\n wind_velocity = wv_new$wind_velocity,\n linear, parabola, asymptote\n)\n```\n:::\n\n\n\n## `my_fits`\n\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 31 x 4\n wind_velocity linear parabola asymptote\n \n 1 1 0.372 -0.471 -3.96 \n 2 1.5 0.493 -0.157 -1.64 \n 3 2 0.613 0.137 -0.488\n 4 2.5 0.734 0.413 0.205\n 5 3 0.854 0.670 0.667\n 6 3.5 0.975 0.907 0.998\n 7 4 1.10 1.13 1.25 \n 8 4.5 1.22 1.33 1.44 \n 9 5 1.34 1.51 1.59 \n10 5.5 1.46 1.67 1.72 \n# i 21 more rows\n```\n:::\n:::\n\n\n\n\n## Making a plot 1/2\n\n- To make a plot, we use the same trick as last time to get all three\npredictions on a plot with a legend (saving result to add to later):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits %>%\n pivot_longer(\n linear:asymptote,\n names_to=\"model\", \n values_to=\"fit\"\n ) %>%\n ggplot(aes(\n y = fit, x = wind_velocity,\n colour = model\n )) + geom_line() -> g\n```\n:::\n\n\n\n## Making a plot 2/2\n\n- The observed wind velocities were in this range:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(vels <- range(windmill$wind_velocity))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.45 10.20\n```\n:::\n:::\n\n\n\n- `DC.output` between 0 and 3 from asymptote model. Add rectangle to\ngraph around where the data were:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng + geom_rect(\n xmin = vels[1], xmax = vels[2], ymin = 0, ymax = 3,\n alpha=0, colour = \"black\"\n)\n```\n:::\n\n\n\n## The plot\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-35-1.pdf)\n:::\n:::\n\n\n\n## Comments (1)\n- Over range of data, two models agree with each other well.\n- Outside range of data, they disagree violently!\n- For larger `wind.velocity`, asymptote model behaves reasonably,\nparabola model does not.\n- What happens as `wind.velocity` goes to zero? Should find\n`DC.output` goes to zero as well. Does it?\n\n## Comments (2)\n\n- For parabola model:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.16 0.175 -6.62 1.18e- 6\n2 wind_velocity 0.723 0.0614 11.8 5.77e-11\n3 I(wind_velocity^2) -0.0381 0.00480 -7.95 6.59e- 8\n```\n:::\n:::\n\n\n\n- Nope, goes to −1.16 (intercept), actually significantly different from\nzero.\n\n## Comments (3): asymptote model\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 2.98 0.0449 66.3 8.92e-28\n2 wind_pace -6.93 0.206 -33.6 4.74e-21\n```\n:::\n:::\n\n\n\\normalsize\n\n- As `wind.velocity` heads to 0, wind.pace heads to $+\\infty$, so\nDC.output heads to $−\\infty$! \n- Also need more data for small `wind.velocity` to understand\nrelationship. (Is there a lower asymptote?)\n- Best we can do now is to predict `DC.output` to be zero for small\n`wind.velocity`.\n- Assumes a “threshold” wind velocity below which no electricity\ngenerated at all.\n\n## Summary\n- Often, in data analysis, there is no completely satisfactory conclusion,\nas here.\n- Have to settle for model that works OK, with restrictions.\n- Always something else you can try.\n- At some point you have to say “I stop.”\n\n\n\n", + "markdown": "---\ntitle: \"Case study: windmill\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## The windmill data\n\n- Engineer: does amount of electricity generated by windmill depend on\n how strongly wind blowing?\n- Measurements of wind speed and DC current generated at various\n times.\n- Assume the \"various times\" to be randomly selected --- aim to\n generalize to \"this windmill at all times\".\n- Research questions:\n - Relationship between wind speed and current generated?\n - If so, what kind of relationship?\n - Can we model relationship to do predictions?\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n## Reading in the data\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \n \"http://ritsokiguess.site/datafiles/windmill.csv\"\nwindmill <- read_csv(my_url)\nwindmill\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 25 x 2\n wind_velocity DC_output\n \n 1 5 1.58 \n 2 6 1.82 \n 3 3.4 1.06 \n 4 2.7 0.5 \n 5 10 2.24 \n 6 9.7 2.39 \n 7 9.55 2.29 \n 8 3.05 0.558\n 9 8.15 2.17 \n10 6.2 1.87 \n# i 15 more rows\n```\n:::\n:::\n\n\n\n## Strategy\n\n- Two quantitative variables, looking for relationship: regression\n methods.\n- Start with picture (scatterplot).\n- Fit models and do model checking, fixing up things as necessary.\n- Scatterplot:\n - 2 variables, `DC_output` and `wind_velocity`.\n - First is output/response, other is input/explanatory.\n - Put `DC_output` on vertical scale.\n- Add trend, but don't want to assume linear:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth() \n```\n:::\n\n\n\n## Scatterplot\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-5-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Definitely a relationship: as wind velocity increases, so does DC\n output. (As you'd expect.)\n- Is relationship linear? To help judge, `geom_smooth` smooths\n scatterplot trend. (Trend called \"loess\", \"Locally weighted least\n squares\" which downweights outliers. Not constrained to be\n straight.)\n- Trend more or less linear for while, then curves downwards\n (levelling off?). Straight line not so good here.\n\n## Fit a straight line (and see what happens)\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.1 <- lm(DC_output ~ wind_velocity, data = windmill)\nsummary(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity, data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.59869 -0.14099 0.06059 0.17262 0.32184 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.13088 0.12599 1.039 0.31 \nwind_velocity 0.24115 0.01905 12.659 7.55e-12 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.2361 on 23 degrees of freedom\nMultiple R-squared: 0.8745,\tAdjusted R-squared: 0.869 \nF-statistic: 160.3 on 1 and 23 DF, p-value: 7.546e-12\n```\n:::\n:::\n\n\n\n## Another way of looking at the output\n\n- The standard output tends to go off the bottom of the page rather\n easily. Package `broom` has these:\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.874 0.869 0.236 160. 7.55e-12 1 1.66 2.68 6.33\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\\normalsize\n\nshowing that the R-squared is 87%, and\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 0.131 0.126 1.04 3.10e- 1\n2 wind_velocity 0.241 0.0190 12.7 7.55e-12\n```\n:::\n:::\n\n\n\n\\normalsize\n\nshowing the intercept and slope and their significance.\n\n## Comments\n\n- Strategy: `lm` actually fits the regression. Store results in a\n variable. Then look at the results, eg. via `summary` or\n `glance`/`tidy`.\n- My strategy for model names: base on response variable (or data\n frame name) and a number. Allows me to fit several models to same\n data and keep track of which is which.\n- Results actually pretty good: `wind.velocity` strongly significant,\n R-squared (87%) high.\n- How to check whether regression is appropriate? Look at the\n residuals, observed minus predicted, plotted against fitted\n (predicted).\n- Plot using the regression object as \"data frame\" (in a couple of\n slides).\n\n## Scatterplot, but with line\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method=\"lm\", se = FALSE)\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-11-1.pdf)\n:::\n:::\n\n\n\n## Plot of residuals against fitted values\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.1, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-13-1.pdf)\n:::\n:::\n\n\n\n## Comments on residual plot\n\n- Residual plot should be a random scatter of points.\n- Should be no pattern \"left over\" after fitting the regression.\n- Smooth trend should be more or less straight across at 0.\n- Here, have a curved trend on residual plot.\n- This means original relationship must have been a curve (as we saw\n on original scatterplot).\n- Possible ways to fit a curve:\n - Add a squared term in explanatory variable.\n - Transform response variable (doesn't work well here).\n - See what science tells you about mathematical form of\n relationship, and try to apply.\n\n## normal quantile plot of residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/unnamed-chunk-1-1.pdf)\n:::\n:::\n\n\n\n## Parabolas and fitting parabola model\n\n- A parabola has equation $$y = ax^2 + bx + c$$ with coefficients\n $a, b, c$. About the simplest function that is not a straight line.\n- Fit one using `lm` by adding $x^2$ to right side of model formula\n with +:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.2 <- lm(DC_output ~ wind_velocity + I(wind_velocity^2),\n data = windmill\n)\n```\n:::\n\n\n\n- The `I()` necessary because `^` in model formula otherwise means\n something different (to do with interactions in ANOVA).\n- Call it *parabola model*.\n\n## Parabola model output\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity + I(wind_velocity^2), \n data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.26347 -0.02537 0.01264 0.03908 0.19903 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.155898 0.174650 -6.618 1.18e-06 ***\nwind_velocity 0.722936 0.061425 11.769 5.77e-11 ***\nI(wind_velocity^2) -0.038121 0.004797 -7.947 6.59e-08 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.1227 on 22 degrees of freedom\nMultiple R-squared: 0.9676,\tAdjusted R-squared: 0.9646 \nF-statistic: 328.3 on 2 and 22 DF, p-value: < 2.2e-16\n```\n:::\n\n```{.r .cell-code}\n# tidy(DC.2)\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_velocity + I(wind_velocity^2), \n data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.26347 -0.02537 0.01264 0.03908 0.19903 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.155898 0.174650 -6.618 1.18e-06 ***\nwind_velocity 0.722936 0.061425 11.769 5.77e-11 ***\nI(wind_velocity^2) -0.038121 0.004797 -7.947 6.59e-08 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.1227 on 22 degrees of freedom\nMultiple R-squared: 0.9676,\tAdjusted R-squared: 0.9646 \nF-statistic: 328.3 on 2 and 22 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.968 0.965 0.123 328. 4.16e-17 2 18.6 -29.2 -24.3\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\\normalsize\n\n## Comments on output\n\n- R-squared has gone up a lot, from 87% (line) to 97% (parabola).\n- Coefficient of squared term strongly significant (P-value\n $6.59 \\times 10^{−8}$).\n- Adding squared term has definitely improved fit of model.\n- Parabola model better than linear one.\n- But...need to check residuals again.\n\n## Residual plot from parabola model\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.2, aes(y = .resid, x = .fitted)) +\n geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-18-1.pdf)\n:::\n:::\n\n\n\n## normal quantile plot of residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.2, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/unnamed-chunk-3-1.pdf)\n:::\n:::\n\n\n\nThis distribution has long tails, which should worry us at least some.\n\n## Make scatterplot with fitted line and curve\n\n- Residual plot basically random. Good.\n- Scatterplot with fitted line and curve like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(windmill, aes(y = DC_output, x = wind_velocity)) +\n geom_point() + geom_smooth(method = \"lm\", se = F) +\n geom_line(data = DC.2, aes(y = .fitted))\n```\n:::\n\n\n\n## Comments\n\n- This plots:\n - scatterplot (`geom_point`);\n - straight line (via tweak to `geom_smooth`, which draws\n best-fitting line);\n - fitted curve, using the predicted `DC_output` values, joined by\n lines (with points not shown).\n- Trick in the `geom_line` is use the predictions as the `y`-points to\n join by lines (from `DC.2`), instead of the original data points.\n Without the `data` and `aes` in the `geom_line`, original data\n points would be joined by lines.\n\n## Scatterplot with fitted line and curve\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-19-1.pdf)\n:::\n:::\n\n\n\nCurve clearly fits better than line.\n\n## Another approach to a curve\n\n- There is a problem with parabolas, which we'll see later.\n\n- Ask engineer, \"what should happen as wind velocity increases?\":\n\n - Upper limit on electricity generated, but otherwise, the larger\n the wind velocity, the more electricity generated.\n\n- Mathematically, *asymptote*. Straight lines and parabolas don't have\n them, but eg. $y = 1/x$ does: as $x$ gets bigger, $y$ approaches\n zero without reaching it.\n\n- What happens to $y = a + b(1/x)$ as $x$ gets large?\n\n - $y$ gets closer and closer to $a$: that is, $a$ is asymptote.\n\n- Fit this, call it asymptote model.\n\n- Fitting the model here because we have math to justify it.\n\n - Alternative, $y = a + be^{−x}$ , approaches asymptote faster.\n\n## How to fit asymptote model?\n\n- Define new explanatory variable to be $1/x$, and predict $y$ from\n it.\n- $x$ is velocity, distance over time.\n- So $1/x$ is time over distance. In walking world, if you walk 5\n km/h, take 12 minutes to walk 1 km, called your pace. So 1 over\n `wind_velocity` we call `wind_pace`.\n- Make a scatterplot first to check for straightness (next page).\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwindmill %>% mutate(wind_pace = 1 / wind_velocity) -> windmill\nggplot(windmill, aes(y = DC_output, x = wind_pace)) +\n geom_point() + geom_smooth(se = F)\n```\n:::\n\n\n\n- and run regression like this (output page after):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nDC.3 <- lm(DC_output ~ wind_pace, data = windmill)\nsummary(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = DC_output ~ wind_pace, data = windmill)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.20547 -0.04940 0.01100 0.08352 0.12204 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 2.9789 0.0449 66.34 <2e-16 ***\nwind_pace -6.9345 0.2064 -33.59 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.09417 on 23 degrees of freedom\nMultiple R-squared: 0.98,\tAdjusted R-squared: 0.9792 \nF-statistic: 1128 on 1 and 23 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n## Scatterplot for wind_pace\n\nPretty straight. Blue actually smooth curve not line:\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-20-1.pdf)\n:::\n:::\n\n\n\n## Regression output\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nglance(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.980 0.979 0.0942 1128. 4.74e-21 1 24.6 -43.3 -39.6\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\\normalsize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 2.98 0.0449 66.3 8.92e-28\n2 wind_pace -6.93 0.206 -33.6 4.74e-21\n```\n:::\n:::\n\n\n\n## Comments\n\n- R-squared, 98%, even higher than for parabola model (97%).\n- Simpler model, only one explanatory variable (`wind.pace`) vs. 2 for\n parabola model (`wind.velocity` and its square).\n- `wind.pace` (unsurprisingly) strongly significant.\n- Looks good, but check residual plot (over).\n\n## Residual plot for asymptote model\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.3, aes(y = .resid, x = .fitted)) + geom_point()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/resida-1.pdf)\n:::\n:::\n\n\n\n## normal quantile plot of residuals\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nggplot(DC.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()\n```\n\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/unnamed-chunk-4-1.pdf)\n:::\n:::\n\n\n\nThis is skewed (left), but is not bad (and definitely better than the\none for the parabola model).\n\n## Plotting trends on scatterplot\n\n- Residual plot not bad. But residuals go up to 0.10 and down to\n −0.20, suggesting possible skewness (not normal). I think it's not\n perfect, but OK overall.\n- Next: plot scatterplot with all three fitted lines/curves on it (for\n comparison), with legend saying which is which.\n- First make data frame containing what we need, taken from the right\n places:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 <- tibble(\n wind_velocity = windmill$wind_velocity,\n DC_output = windmill$DC_output,\n linear = fitted(DC.1),\n parabola = fitted(DC.2),\n asymptote = fitted(DC.3)\n)\n```\n:::\n\n\n\n## What's in `w2`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 25 x 5\n wind_velocity DC_output linear parabola asymptote\n \n 1 5 1.58 1.34 1.51 1.59 \n 2 6 1.82 1.58 1.81 1.82 \n 3 3.4 1.06 0.951 0.861 0.939\n 4 2.7 0.5 0.782 0.518 0.411\n 5 10 2.24 2.54 2.26 2.29 \n 6 9.7 2.39 2.47 2.27 2.26 \n 7 9.55 2.29 2.43 2.27 2.25 \n 8 3.05 0.558 0.866 0.694 0.705\n 9 8.15 2.17 2.10 2.20 2.13 \n10 6.2 1.87 1.63 1.86 1.86 \n# i 15 more rows\n```\n:::\n:::\n\n\n\n## Making the plot\n\n- `ggplot` likes to have one column of $x$'s to plot, and one column\n of $y$'s, with another column for distinguishing things.\n- But we have three columns of fitted values, that need to be combined\n into one.\n- `pivot_longer`, then plot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nw2 %>%\n pivot_longer(linear:asymptote, names_to=\"model\", \n values_to=\"fit\") %>%\n ggplot(aes(x = wind_velocity, y = DC_output)) +\n geom_point() +\n geom_line(aes(y = fit, colour = model)) \n```\n:::\n\n\n\n## Scatterplot with fitted curves\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-25-1.pdf)\n:::\n:::\n\n\n\n## Comments\n\n- Predictions from curves are very similar.\n- Predictions from asymptote model as good, and from simpler model\n (one $x$ not two), so prefer those.\n- Go back to asymptote model summary.\n\n## Asymptote model summary\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 2.98 0.0449 66.3 8.92e-28\n2 wind_pace -6.93 0.206 -33.6 4.74e-21\n```\n:::\n:::\n\n\n\n## Comments\n\n- Intercept in this model about 3.\n- Intercept of asymptote model is the asymptote (upper limit of\n `DC.output`).\n- Not close to asymptote yet.\n- Therefore, from this model, wind could get stronger and would\n generate appreciably more electricity.\n- This is extrapolation! Would like more data from times when\n `wind.velocity` higher.\n- Slope −7. Why negative?\n - As wind.velocity increases, wind.pace goes down, and DC.output\n goes up. Check.\n- Actual slope number hard to interpret.\n\n## Checking back in with research questions\n\n- Is there a relationship between wind speed and current generated?\n - Yes.\n- If so, what kind of relationship is it?\n - One with an asymptote.\n- Can we model the relationship, in such a way that we can do\n predictions?\n - Yes, see model DC.3 and plot of fitted curve.\n- Good. Job done.\n\n## Job done, kinda\n\n- Just because the parabola model and asymptote model agree over the\n range of the data, doesn't necessarily mean they agree everywhere.\n- Extend range of wind.velocity to 1 to 16 (steps of 0.5), and predict\n DC.output according to the two models:\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nwv <- seq(1, 16, 0.5)\nwv\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0\n[14] 7.5 8.0 8.5 9.0 9.5 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5\n[27] 14.0 14.5 15.0 15.5 16.0\n```\n:::\n:::\n\n\n\n- R has `predict`, which requires what to predict for, as data frame.\n The data frame has to contain values, with matching names, for all\n explanatory variables in regression(s).\n\n## Setting up data frame to predict from\n\n- Linear model had just `wind_velocity`.\n- Parabola model had that as well (squared one will be calculated)\n- Asymptote model had just `wind_pace` (reciprocal of velocity).\n- So create data frame called `wv_new` with those in:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new <- tibble(wind_velocity = wv, wind_pace = 1 / wv)\n```\n:::\n\n\n\n## `wv_new`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwv_new\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 31 x 2\n wind_velocity wind_pace\n \n 1 1 1 \n 2 1.5 0.667\n 3 2 0.5 \n 4 2.5 0.4 \n 5 3 0.333\n 6 3.5 0.286\n 7 4 0.25 \n 8 4.5 0.222\n 9 5 0.2 \n10 5.5 0.182\n# i 21 more rows\n```\n:::\n:::\n\n\n\n## Doing predictions, one for each model\n\n- Use same names as before:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlinear <- predict(DC.1, wv_new)\nparabola <- predict(DC.2, wv_new)\nasymptote <- predict(DC.3, wv_new)\n```\n:::\n\n\n\n- Put it all into a data frame for plotting, along with original data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits <- tibble(\n wind_velocity = wv_new$wind_velocity,\n linear, parabola, asymptote\n)\n```\n:::\n\n\n\n## `my_fits`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 31 x 4\n wind_velocity linear parabola asymptote\n \n 1 1 0.372 -0.471 -3.96 \n 2 1.5 0.493 -0.157 -1.64 \n 3 2 0.613 0.137 -0.488\n 4 2.5 0.734 0.413 0.205\n 5 3 0.854 0.670 0.667\n 6 3.5 0.975 0.907 0.998\n 7 4 1.10 1.13 1.25 \n 8 4.5 1.22 1.33 1.44 \n 9 5 1.34 1.51 1.59 \n10 5.5 1.46 1.67 1.72 \n# i 21 more rows\n```\n:::\n:::\n\n\n\n## Making a plot 1/2\n\n- To make a plot, we use the same trick as last time to get all three\n predictions on a plot with a legend (saving result to add to later):\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_fits %>%\n pivot_longer(\n linear:asymptote,\n names_to=\"model\", \n values_to=\"fit\"\n ) %>%\n ggplot(aes(\n y = fit, x = wind_velocity,\n colour = model\n )) + geom_line() -> g\n```\n:::\n\n\n\n## Making a plot 2/2\n\n- The observed wind velocities were in this range:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n(vels <- range(windmill$wind_velocity))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 2.45 10.20\n```\n:::\n:::\n\n\n\n- `DC.output` between 0 and 3 from asymptote model. Add rectangle to\n graph around where the data were:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng + geom_rect(\n xmin = vels[1], xmax = vels[2], ymin = 0, ymax = 3,\n alpha=0, colour = \"black\"\n)\n```\n:::\n\n\n\n## The plot\n\n\n\n::: {.cell}\n::: {.cell-output-display}\n![](windmill_files/figure-beamer/windmill-35-1.pdf)\n:::\n:::\n\n\n\n## Comments (1)\n\n- Over range of data, two models agree with each other well.\n- Outside range of data, they disagree violently!\n- For larger `wind.velocity`, asymptote model behaves reasonably,\n parabola model does not.\n- What happens as `wind.velocity` goes to zero? Should find\n `DC.output` goes to zero as well. Does it?\n\n## Comments (2)\n\n- For parabola model:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 3 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) -1.16 0.175 -6.62 1.18e- 6\n2 wind_velocity 0.723 0.0614 11.8 5.77e-11\n3 I(wind_velocity^2) -0.0381 0.00480 -7.95 6.59e- 8\n```\n:::\n:::\n\n\n\n- Nope, goes to −1.16 (intercept), actually significantly different\n from zero.\n\n## Comments (3): asymptote model\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntidy(DC.3)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 2.98 0.0449 66.3 8.92e-28\n2 wind_pace -6.93 0.206 -33.6 4.74e-21\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- As `wind.velocity` heads to 0, wind.pace heads to $+\\infty$, so\n DC.output heads to $−\\infty$!\n- Also need more data for small `wind.velocity` to understand\n relationship. (Is there a lower asymptote?)\n- Best we can do now is to predict `DC.output` to be zero for small\n `wind.velocity`.\n- Assumes a \"threshold\" wind velocity below which no electricity\n generated at all.\n\n## Summary\n\n- Often, in data analysis, there is no completely satisfactory\n conclusion, as here.\n- Have to settle for model that works OK, with restrictions.\n- Always something else you can try.\n- At some point you have to say \"I stop.\"\n", "supporting": [ "windmill_files/figure-beamer" ], diff --git a/_freeze/windmill/figure-beamer/resida-1.pdf b/_freeze/windmill/figure-beamer/resida-1.pdf index 47becd8..87347cf 100644 Binary files a/_freeze/windmill/figure-beamer/resida-1.pdf and b/_freeze/windmill/figure-beamer/resida-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/unnamed-chunk-1-1.pdf b/_freeze/windmill/figure-beamer/unnamed-chunk-1-1.pdf new file mode 100644 index 0000000..383ead1 Binary files /dev/null and b/_freeze/windmill/figure-beamer/unnamed-chunk-1-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/unnamed-chunk-3-1.pdf b/_freeze/windmill/figure-beamer/unnamed-chunk-3-1.pdf new file mode 100644 index 0000000..df03d16 Binary files /dev/null and b/_freeze/windmill/figure-beamer/unnamed-chunk-3-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/unnamed-chunk-4-1.pdf b/_freeze/windmill/figure-beamer/unnamed-chunk-4-1.pdf new file mode 100644 index 0000000..b1dc695 Binary files /dev/null and b/_freeze/windmill/figure-beamer/unnamed-chunk-4-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-11-1.pdf b/_freeze/windmill/figure-beamer/windmill-11-1.pdf index 72b278b..847abbd 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-11-1.pdf and b/_freeze/windmill/figure-beamer/windmill-11-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-13-1.pdf b/_freeze/windmill/figure-beamer/windmill-13-1.pdf index 0393c1b..a283919 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-13-1.pdf and b/_freeze/windmill/figure-beamer/windmill-13-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-18-1.pdf b/_freeze/windmill/figure-beamer/windmill-18-1.pdf index 2841e90..b762588 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-18-1.pdf and b/_freeze/windmill/figure-beamer/windmill-18-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-19-1.pdf b/_freeze/windmill/figure-beamer/windmill-19-1.pdf index 3576f5a..87bf271 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-19-1.pdf and b/_freeze/windmill/figure-beamer/windmill-19-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-20-1.pdf b/_freeze/windmill/figure-beamer/windmill-20-1.pdf index 1c6d6ad..79a1d4d 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-20-1.pdf and b/_freeze/windmill/figure-beamer/windmill-20-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-25-1.pdf b/_freeze/windmill/figure-beamer/windmill-25-1.pdf index 1d22559..be52843 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-25-1.pdf and b/_freeze/windmill/figure-beamer/windmill-25-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-35-1.pdf b/_freeze/windmill/figure-beamer/windmill-35-1.pdf index ef9201e..6ed3643 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-35-1.pdf and b/_freeze/windmill/figure-beamer/windmill-35-1.pdf differ diff --git a/_freeze/windmill/figure-beamer/windmill-5-1.pdf b/_freeze/windmill/figure-beamer/windmill-5-1.pdf index 0b7cf39..0923312 100644 Binary files a/_freeze/windmill/figure-beamer/windmill-5-1.pdf and b/_freeze/windmill/figure-beamer/windmill-5-1.pdf differ diff --git a/_freeze/windmill/figure-revealjs/unnamed-chunk-1-1.png b/_freeze/windmill/figure-revealjs/unnamed-chunk-1-1.png new file mode 100644 index 0000000..8e04989 Binary files /dev/null and b/_freeze/windmill/figure-revealjs/unnamed-chunk-1-1.png differ diff --git a/_freeze/windmill/figure-revealjs/unnamed-chunk-3-1.png b/_freeze/windmill/figure-revealjs/unnamed-chunk-3-1.png new file mode 100644 index 0000000..c3b3c71 Binary files /dev/null and b/_freeze/windmill/figure-revealjs/unnamed-chunk-3-1.png differ diff --git a/_freeze/windmill/figure-revealjs/unnamed-chunk-4-1.png b/_freeze/windmill/figure-revealjs/unnamed-chunk-4-1.png new file mode 100644 index 0000000..853eb26 Binary files /dev/null and b/_freeze/windmill/figure-revealjs/unnamed-chunk-4-1.png differ diff --git a/_freeze/with_categ/execute-results/html.json b/_freeze/with_categ/execute-results/html.json index 641374a..6b1e927 100644 --- a/_freeze/with_categ/execute-results/html.json +++ b/_freeze/with_categ/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "82b1ff8a0ebdb9d2e4b1972a407e2f6b", + "hash": "6d59330e1205cca45d7f8341b304ad82", "result": { - "markdown": "---\ntitle: \"Regression with categorical variables\"\n---\n\n\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n## The pigs revisited\n\n\n::: {.cell}\n\n:::\n\n\n- Recall pig feed data, after we tidied it: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs2.txt\"\npigs <- read_delim(my_url, \" \")\npigs \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Summaries\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>%\n group_by(feed) %>%\n summarize(n = n(), mean_wt = mean(weight), \n sd_wt = sd(weight))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Running through `aov` and `lm`\n- What happens if we run this through `lm` rather than `aov`? \n- Recall `aov` first: \n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.1 <- aov(weight ~ feed, data = pigs)\nsummary(pigs.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.9 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n## and now `lm`\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.2 <- lm(weight ~ feed, data = pigs)\ntidy(pigs.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nglance(pigs.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n## Understanding those slopes {.scrollable}\n- Get one slope for each category of categorical variable feed, except\nfor first.\n- feed1 treated as “baseline”, others measured relative to that.\n- Thus prediction for feed 1 is intercept, 60.62 (mean weight for feed 1).\n- Prediction for feed 2 is 60.62 + 8.68 = 69.30 (mean weight for feed 2).\n- Or, mean weight for feed 2 is 8.68 bigger than for feed 1.\n- Mean weight for feed 3 is 33.48 bigger than for feed 1.\n- Slopes can be negative, if mean for a feed had been smaller than for\nfeed 1.\n\n## Reproducing the ANOVA\n- Pass the fitted model object into `anova`:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(pigs.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\\normalsize\n\n- Same as before.\n- But no Tukey this way:\n\n\\footnotesize\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(pigs.2)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in UseMethod(\"TukeyHSD\"): no applicable method for 'TukeyHSD' applied to an object of class \"lm\"\n```\n:::\n:::\n\n\\normalsize\n\n## The crickets\n- Male crickets rub their wings together to produce a chirping sound.\n- Rate of chirping, called “pulse rate”, depends on species and possibly\non temperature.\n- Sample of crickets of two species’ pulse rates measured; temperature\nalso recorded.\n- Does pulse rate differ for species, especially when temperature\naccounted for?\n\n## The crickets data\nRead the data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/crickets2.csv\"\ncrickets <- read_csv(my_url)\ncrickets %>% sample_n(10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Fit model with `lm` \n\n\n::: {.cell}\n\n```{.r .cell-code}\ncrickets.1 <- lm(pulse_rate ~ temperature + species, \n data = crickets)\n```\n:::\n\n\nCan I remove anything? No:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(crickets.1, test = \"F\") \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n`drop1` is right thing to use in a regression with categorical (explanatory) variables in it: \"can I remove this categorical variable *as a whole*?\"\n\n## The summary \n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(crickets.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = pulse_rate ~ temperature + species, data = crickets)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.0128 -1.1296 -0.3912 0.9650 3.7800 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -7.21091 2.55094 -2.827 0.00858 ** \ntemperature 3.60275 0.09729 37.032 < 2e-16 ***\nspeciesniveus -10.06529 0.73526 -13.689 6.27e-14 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.786 on 28 degrees of freedom\nMultiple R-squared: 0.9896,\tAdjusted R-squared: 0.9888 \nF-statistic: 1331 on 2 and 28 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n## Conclusions\n\n - Slope for temperature says that increasing temperature by 1 degree\nincreases pulse rate by 3.6 (same for both species)\n- Slope for `speciesniveus` says that pulse rate for `niveus` about 10\nlower than that for `exclamationis` at same temperature (latter species\nis baseline).\n- R-squared of almost 0.99 is very high, so that the prediction of pulse\nrate from species and temperature is very good.\n\n## To end with a graph\n\n- Two quantitative variables and one categorical: scatterplot with categories distinguished by colour.\n- This graph seems to need a title, which I define first. \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt1 <- \"Pulse rate against temperature for two species of crickets\"\nt2 <- \"Temperature in degrees Celsius\"\nggplot(crickets, aes(x = temperature, y = pulse_rate,\n colour = species)) +\n geom_point() + geom_smooth(method = \"lm\", se = FALSE) +\n ggtitle(t1, t2) -> g\n```\n:::\n\n\n## The graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](with_categ_files/figure-revealjs/unnamed-chunk-1-1.png){width=960}\n:::\n:::\n", + "markdown": "---\ntitle: \"Regression with categorical variables\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n## Packages for this section\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n## The pigs revisited\n\n\n::: {.cell}\n\n:::\n\n\n- Recall pig feed data, after we tidied it:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs2.txt\"\npigs <- read_delim(my_url, \" \")\npigs \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Summaries\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>%\n group_by(feed) %>%\n summarize(n = n(), mean_wt = mean(weight), \n sd_wt = sd(weight))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Running through `aov` and `lm`\n\n- What happens if we run this through `lm` rather than `aov`?\n- Recall `aov` first:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.1 <- aov(weight ~ feed, data = pigs)\nsummary(pigs.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.9 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n## and now `lm`\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.2 <- lm(weight ~ feed, data = pigs)\nsummary(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = weight ~ feed, data = pigs)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.900 -2.025 -0.570 1.845 5.000 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 60.620 1.404 43.190 < 2e-16 ***\nfeedfeed2 8.680 1.985 4.373 0.000473 ***\nfeedfeed3 33.480 1.985 16.867 1.30e-11 ***\nfeedfeed4 25.620 1.985 12.907 7.11e-10 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 3.138 on 16 degrees of freedom\nMultiple R-squared: 0.9572,\tAdjusted R-squared: 0.9491 \nF-statistic: 119.1 on 3 and 16 DF, p-value: 3.72e-11\n```\n:::\n\n```{.r .cell-code}\ntidy(pigs.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n\n```{.r .cell-code}\nglance(pigs.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n## Understanding those slopes {.scrollable}\n\n- Get one slope for each category of categorical variable feed, except\n for first.\n- feed1 treated as \"baseline\", others measured relative to that.\n- Thus prediction for feed 1 is intercept, 60.62 (mean weight for feed\n 1).\n- Prediction for feed 2 is 60.62 + 8.68 = 69.30 (mean weight for feed\n 2).\n- Or, mean weight for feed 2 is 8.68 bigger than for feed 1.\n- Mean weight for feed 3 is 33.48 bigger than for feed 1.\n- Slopes can be negative, if mean for a feed had been smaller than for\n feed 1.\n\n## Reproducing the ANOVA\n\n- Pass the fitted model object into `anova`:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(pigs.2)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n\\normalsize\n\n- Same as before.\n- But no Tukey this way:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(pigs.2)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in UseMethod(\"TukeyHSD\"): no applicable method for 'TukeyHSD' applied to an object of class \"lm\"\n```\n:::\n:::\n\n\n\\normalsize\n\n## The crickets\n\n- Male crickets rub their wings together to produce a chirping sound.\n- Rate of chirping, called \"pulse rate\", depends on species and\n possibly on temperature.\n- Sample of crickets of two species' pulse rates measured; temperature\n also recorded.\n- Does pulse rate differ for species, especially when temperature\n accounted for?\n\n## The crickets data\n\nRead the data:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/crickets2.csv\"\ncrickets <- read_csv(my_url)\ncrickets %>% slice_sample(n = 10)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n## Fit model with `lm`\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncrickets.1 <- lm(pulse_rate ~ temperature + species, \n data = crickets)\n```\n:::\n\n\nCan I remove anything? No:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(crickets.1, test = \"F\") \n```\n\n::: {.cell-output-display}\n`````{=html}\n
\n \n
\n`````\n:::\n:::\n\n\n`drop1` is right thing to use in a regression with categorical\n(explanatory) variables in it: \"can I remove this categorical variable\n*as a whole*?\"\n\n## The summary\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(crickets.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = pulse_rate ~ temperature + species, data = crickets)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.0128 -1.1296 -0.3912 0.9650 3.7800 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -7.21091 2.55094 -2.827 0.00858 ** \ntemperature 3.60275 0.09729 37.032 < 2e-16 ***\nspeciesniveus -10.06529 0.73526 -13.689 6.27e-14 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.786 on 28 degrees of freedom\nMultiple R-squared: 0.9896,\tAdjusted R-squared: 0.9888 \nF-statistic: 1331 on 2 and 28 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n## Conclusions\n\n- Slope for temperature says that increasing temperature by 1 degree\n increases pulse rate by 3.6 (same for both species)\n- Slope for `speciesniveus` says that pulse rate for `niveus` about 10\n lower than that for `exclamationis` at same temperature (latter\n species is baseline).\n- R-squared of almost 0.99 is very high, so that the prediction of\n pulse rate from species and temperature is very good.\n\n## To end with a graph\n\n- Two quantitative variables and one categorical: scatterplot with\n categories distinguished by colour.\n- This graph seems to need a title, which I define first.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt1 <- \"Pulse rate against temperature for two species of crickets\"\nt2 <- \"Temperature in degrees Celsius\"\nggplot(crickets, aes(x = temperature, y = pulse_rate,\n colour = species)) +\n geom_point() + geom_smooth(method = \"lm\", se = FALSE) +\n ggtitle(t1, t2) -> g\n```\n:::\n\n\n## The graph\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](with_categ_files/figure-revealjs/unnamed-chunk-1-1.png){width=960}\n:::\n:::\n", "supporting": [ "with_categ_files/figure-revealjs" ], diff --git a/_freeze/with_categ/execute-results/tex.json b/_freeze/with_categ/execute-results/tex.json index 6846b99..dfa2003 100644 --- a/_freeze/with_categ/execute-results/tex.json +++ b/_freeze/with_categ/execute-results/tex.json @@ -1,7 +1,7 @@ { - "hash": "82b1ff8a0ebdb9d2e4b1972a407e2f6b", + "hash": "6d59330e1205cca45d7f8341b304ad82", "result": { - "markdown": "---\ntitle: \"Regression with categorical variables\"\n---\n\n\n\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n\n## The pigs revisited\n\n\n\n::: {.cell}\n\n:::\n\n\n\n- Recall pig feed data, after we tidied it: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs2.txt\"\npigs <- read_delim(my_url, \" \")\npigs \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 2 feed1 57 \n 3 3 feed1 65 \n 4 4 feed1 58.6\n 5 5 feed1 61.7\n 6 1 feed2 68.7\n 7 2 feed2 67.7\n 8 3 feed2 74 \n 9 4 feed2 66.3\n10 5 feed2 69.8\n11 1 feed3 92.6\n12 2 feed3 92.1\n13 3 feed3 90.2\n14 4 feed3 96.5\n15 5 feed3 99.1\n16 1 feed4 87.9\n17 2 feed4 84.2\n18 3 feed4 83.1\n19 4 feed4 85.7\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Summaries\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>%\n group_by(feed) %>%\n summarize(n = n(), mean_wt = mean(weight), \n sd_wt = sd(weight))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 4\n feed n mean_wt sd_wt\n \n1 feed1 5 60.6 3.06\n2 feed2 5 69.3 2.93\n3 feed3 5 94.1 3.61\n4 feed4 5 86.2 2.90\n```\n:::\n:::\n\n\n\n## Running through `aov` and `lm`\n- What happens if we run this through `lm` rather than `aov`? \n- Recall `aov` first: \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.1 <- aov(weight ~ feed, data = pigs)\nsummary(pigs.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.9 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n## and now `lm`\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.2 <- lm(weight ~ feed, data = pigs)\ntidy(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 60.6 1.40 43.2 5.39e-18\n2 feedfeed2 8.68 1.98 4.37 4.73e- 4\n3 feedfeed3 33.5 1.98 16.9 1.30e-11\n4 feedfeed4 25.6 1.98 12.9 7.11e-10\n```\n:::\n\n```{.r .cell-code}\nglance(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.957 0.949 3.14 119. 3.72e-11 3 -49.0 108. 113.\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\\normalsize\n\n## Understanding those slopes {.scrollable}\n- Get one slope for each category of categorical variable feed, except\nfor first.\n- feed1 treated as “baseline”, others measured relative to that.\n- Thus prediction for feed 1 is intercept, 60.62 (mean weight for feed 1).\n- Prediction for feed 2 is 60.62 + 8.68 = 69.30 (mean weight for feed 2).\n- Or, mean weight for feed 2 is 8.68 bigger than for feed 1.\n- Mean weight for feed 3 is 33.48 bigger than for feed 1.\n- Slopes can be negative, if mean for a feed had been smaller than for\nfeed 1.\n\n## Reproducing the ANOVA\n- Pass the fitted model object into `anova`:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nResponse: weight\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3520.5 1173.51 119.14 3.72e-11 ***\nResiduals 16 157.6 9.85 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\\normalsize\n\n- Same as before.\n- But no Tukey this way:\n\n\\footnotesize\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(pigs.2)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in UseMethod(\"TukeyHSD\"): no applicable method for 'TukeyHSD' applied to an object of class \"lm\"\n```\n:::\n:::\n\n\n\\normalsize\n\n## The crickets\n- Male crickets rub their wings together to produce a chirping sound.\n- Rate of chirping, called “pulse rate”, depends on species and possibly\non temperature.\n- Sample of crickets of two species’ pulse rates measured; temperature\nalso recorded.\n- Does pulse rate differ for species, especially when temperature\naccounted for?\n\n## The crickets data\nRead the data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/crickets2.csv\"\ncrickets <- read_csv(my_url)\ncrickets %>% sample_n(10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 3\n species temperature pulse_rate\n \n 1 niveus 17.2 44.3\n 2 exclamationis 30.4 102. \n 3 niveus 26.5 77 \n 4 exclamationis 24 78.7\n 5 niveus 18.9 51.8\n 6 niveus 22.1 60.7\n 7 exclamationis 24 77.3\n 8 exclamationis 24 79.4\n 9 exclamationis 24 80.4\n10 exclamationis 26.2 86.6\n```\n:::\n:::\n\n\n\n## Fit model with `lm` \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncrickets.1 <- lm(pulse_rate ~ temperature + species, \n data = crickets)\n```\n:::\n\n\n\nCan I remove anything? No:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(crickets.1, test = \"F\") \n```\n\n::: {.cell-output .cell-output-stdout}\n```\nSingle term deletions\n\nModel:\npulse_rate ~ temperature + species\n Df Sum of Sq RSS AIC F value Pr(>F) \n 89.3 38.816 \ntemperature 1 4376.1 4465.4 158.074 1371.4 < 2.2e-16 ***\nspecies 1 598.0 687.4 100.065 187.4 6.272e-14 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n`drop1` is right thing to use in a regression with categorical (explanatory) variables in it: \"can I remove this categorical variable *as a whole*?\"\n\n## The summary \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(crickets.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = pulse_rate ~ temperature + species, data = crickets)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.0128 -1.1296 -0.3912 0.9650 3.7800 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -7.21091 2.55094 -2.827 0.00858 ** \ntemperature 3.60275 0.09729 37.032 < 2e-16 ***\nspeciesniveus -10.06529 0.73526 -13.689 6.27e-14 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.786 on 28 degrees of freedom\nMultiple R-squared: 0.9896,\tAdjusted R-squared: 0.9888 \nF-statistic: 1331 on 2 and 28 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n## Conclusions\n\n - Slope for temperature says that increasing temperature by 1 degree\nincreases pulse rate by 3.6 (same for both species)\n- Slope for `speciesniveus` says that pulse rate for `niveus` about 10\nlower than that for `exclamationis` at same temperature (latter species\nis baseline).\n- R-squared of almost 0.99 is very high, so that the prediction of pulse\nrate from species and temperature is very good.\n\n## To end with a graph\n\n- Two quantitative variables and one categorical: scatterplot with categories distinguished by colour.\n- This graph seems to need a title, which I define first. \n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt1 <- \"Pulse rate against temperature for two species of crickets\"\nt2 <- \"Temperature in degrees Celsius\"\nggplot(crickets, aes(x = temperature, y = pulse_rate,\n colour = species)) +\n geom_point() + geom_smooth(method = \"lm\", se = FALSE) +\n ggtitle(t1, t2) -> g\n```\n:::\n\n\n\n## The graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](with_categ_files/figure-beamer/unnamed-chunk-1-1.pdf)\n:::\n:::\n", + "markdown": "---\ntitle: \"Regression with categorical variables\"\neditor: \n markdown: \n wrap: 72\n---\n\n\n\n## Packages for this section\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\nlibrary(broom)\n```\n:::\n\n\n\n## The pigs revisited\n\n\n\n::: {.cell}\n\n:::\n\n\n\n- Recall pig feed data, after we tidied it:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/pigs2.txt\"\npigs <- read_delim(my_url, \" \")\npigs \n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 20 x 3\n pig feed weight\n \n 1 1 feed1 60.8\n 2 2 feed1 57 \n 3 3 feed1 65 \n 4 4 feed1 58.6\n 5 5 feed1 61.7\n 6 1 feed2 68.7\n 7 2 feed2 67.7\n 8 3 feed2 74 \n 9 4 feed2 66.3\n10 5 feed2 69.8\n11 1 feed3 92.6\n12 2 feed3 92.1\n13 3 feed3 90.2\n14 4 feed3 96.5\n15 5 feed3 99.1\n16 1 feed4 87.9\n17 2 feed4 84.2\n18 3 feed4 83.1\n19 4 feed4 85.7\n20 5 feed4 90.3\n```\n:::\n:::\n\n\n\n## Summaries\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs %>%\n group_by(feed) %>%\n summarize(n = n(), mean_wt = mean(weight), \n sd_wt = sd(weight))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 4\n feed n mean_wt sd_wt\n \n1 feed1 5 60.6 3.06\n2 feed2 5 69.3 2.93\n3 feed3 5 94.1 3.61\n4 feed4 5 86.2 2.90\n```\n:::\n:::\n\n\n\n## Running through `aov` and `lm`\n\n- What happens if we run this through `lm` rather than `aov`?\n- Recall `aov` first:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.1 <- aov(weight ~ feed, data = pigs)\nsummary(pigs.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3521 1173.5 119.1 3.72e-11 ***\nResiduals 16 158 9.9 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n## and now `lm`\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npigs.2 <- lm(weight ~ feed, data = pigs)\nsummary(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = weight ~ feed, data = pigs)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.900 -2.025 -0.570 1.845 5.000 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 60.620 1.404 43.190 < 2e-16 ***\nfeedfeed2 8.680 1.985 4.373 0.000473 ***\nfeedfeed3 33.480 1.985 16.867 1.30e-11 ***\nfeedfeed4 25.620 1.985 12.907 7.11e-10 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 3.138 on 16 degrees of freedom\nMultiple R-squared: 0.9572,\tAdjusted R-squared: 0.9491 \nF-statistic: 119.1 on 3 and 16 DF, p-value: 3.72e-11\n```\n:::\n\n```{.r .cell-code}\ntidy(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 5\n term estimate std.error statistic p.value\n \n1 (Intercept) 60.6 1.40 43.2 5.39e-18\n2 feedfeed2 8.68 1.98 4.37 4.73e- 4\n3 feedfeed3 33.5 1.98 16.9 1.30e-11\n4 feedfeed4 25.6 1.98 12.9 7.11e-10\n```\n:::\n\n```{.r .cell-code}\nglance(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 12\n r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC\n \n1 0.957 0.949 3.14 119. 3.72e-11 3 -49.0 108. 113.\n# i 3 more variables: deviance , df.residual , nobs \n```\n:::\n:::\n\n\n\n\\normalsize\n\n## Understanding those slopes {.scrollable}\n\n- Get one slope for each category of categorical variable feed, except\n for first.\n- feed1 treated as \"baseline\", others measured relative to that.\n- Thus prediction for feed 1 is intercept, 60.62 (mean weight for feed\n 1).\n- Prediction for feed 2 is 60.62 + 8.68 = 69.30 (mean weight for feed\n 2).\n- Or, mean weight for feed 2 is 8.68 bigger than for feed 1.\n- Mean weight for feed 3 is 33.48 bigger than for feed 1.\n- Slopes can be negative, if mean for a feed had been smaller than for\n feed 1.\n\n## Reproducing the ANOVA\n\n- Pass the fitted model object into `anova`:\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nanova(pigs.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nAnalysis of Variance Table\n\nResponse: weight\n Df Sum Sq Mean Sq F value Pr(>F) \nfeed 3 3520.5 1173.51 119.14 3.72e-11 ***\nResiduals 16 157.6 9.85 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n\\normalsize\n\n- Same as before.\n- But no Tukey this way:\n\n\\footnotesize\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nTukeyHSD(pigs.2)\n```\n\n::: {.cell-output .cell-output-error}\n```\nError in UseMethod(\"TukeyHSD\"): no applicable method for 'TukeyHSD' applied to an object of class \"lm\"\n```\n:::\n:::\n\n\n\n\\normalsize\n\n## The crickets\n\n- Male crickets rub their wings together to produce a chirping sound.\n- Rate of chirping, called \"pulse rate\", depends on species and\n possibly on temperature.\n- Sample of crickets of two species' pulse rates measured; temperature\n also recorded.\n- Does pulse rate differ for species, especially when temperature\n accounted for?\n\n## The crickets data\n\nRead the data:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/crickets2.csv\"\ncrickets <- read_csv(my_url)\ncrickets %>% slice_sample(n = 10)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 3\n species temperature pulse_rate\n \n 1 exclamationis 26.2 85.8\n 2 niveus 18.3 47.2\n 3 exclamationis 29 101. \n 4 exclamationis 20.8 67.9\n 5 exclamationis 24 78.7\n 6 niveus 28.6 84.7\n 7 exclamationis 26.2 89.1\n 8 niveus 21 58.5\n 9 niveus 21 58.9\n10 niveus 18.9 50.3\n```\n:::\n:::\n\n\n\n## Fit model with `lm`\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncrickets.1 <- lm(pulse_rate ~ temperature + species, \n data = crickets)\n```\n:::\n\n\n\nCan I remove anything? No:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndrop1(crickets.1, test = \"F\") \n```\n\n::: {.cell-output .cell-output-stdout}\n```\nSingle term deletions\n\nModel:\npulse_rate ~ temperature + species\n Df Sum of Sq RSS AIC F value Pr(>F) \n 89.3 38.816 \ntemperature 1 4376.1 4465.4 158.074 1371.4 < 2.2e-16 ***\nspecies 1 598.0 687.4 100.065 187.4 6.272e-14 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n```\n:::\n:::\n\n\n\n`drop1` is right thing to use in a regression with categorical\n(explanatory) variables in it: \"can I remove this categorical variable\n*as a whole*?\"\n\n## The summary\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsummary(crickets.1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n\nCall:\nlm(formula = pulse_rate ~ temperature + species, data = crickets)\n\nResiduals:\n Min 1Q Median 3Q Max \n-3.0128 -1.1296 -0.3912 0.9650 3.7800 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -7.21091 2.55094 -2.827 0.00858 ** \ntemperature 3.60275 0.09729 37.032 < 2e-16 ***\nspeciesniveus -10.06529 0.73526 -13.689 6.27e-14 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.786 on 28 degrees of freedom\nMultiple R-squared: 0.9896,\tAdjusted R-squared: 0.9888 \nF-statistic: 1331 on 2 and 28 DF, p-value: < 2.2e-16\n```\n:::\n:::\n\n\n\n## Conclusions\n\n- Slope for temperature says that increasing temperature by 1 degree\n increases pulse rate by 3.6 (same for both species)\n- Slope for `speciesniveus` says that pulse rate for `niveus` about 10\n lower than that for `exclamationis` at same temperature (latter\n species is baseline).\n- R-squared of almost 0.99 is very high, so that the prediction of\n pulse rate from species and temperature is very good.\n\n## To end with a graph\n\n- Two quantitative variables and one categorical: scatterplot with\n categories distinguished by colour.\n- This graph seems to need a title, which I define first.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nt1 <- \"Pulse rate against temperature for two species of crickets\"\nt2 <- \"Temperature in degrees Celsius\"\nggplot(crickets, aes(x = temperature, y = pulse_rate,\n colour = species)) +\n geom_point() + geom_smooth(method = \"lm\", se = FALSE) +\n ggtitle(t1, t2) -> g\n```\n:::\n\n\n\n## The graph\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ng\n```\n\n::: {.cell-output-display}\n![](with_categ_files/figure-beamer/unnamed-chunk-1-1.pdf)\n:::\n:::\n", "supporting": [ "with_categ_files/figure-beamer" ], diff --git a/_freeze/with_categ/figure-beamer/unnamed-chunk-1-1.pdf b/_freeze/with_categ/figure-beamer/unnamed-chunk-1-1.pdf index 5360a95..e51c409 100644 Binary files a/_freeze/with_categ/figure-beamer/unnamed-chunk-1-1.pdf and b/_freeze/with_categ/figure-beamer/unnamed-chunk-1-1.pdf differ diff --git a/_quarto.yml b/_quarto.yml index 55d961f..cd00e0c 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -6,7 +6,8 @@ project: - readfile.qmd - graphs.qmd - numsum.qmd -# - choosing.qmd + - choosing.qmd + - bootstrap_R.qmd - inference*.qmd - reports.qmd - tidy*.qmd @@ -17,6 +18,23 @@ project: - functions.qmd - dates_and_times.qmd - dip.qmd + - vector_matrix.qmd + - bootstrap.qmd + - stan.qmd + - regression.qmd + - logistic.qmd + - survival.qmd + - anova.qmd + - ancova.qmd + - manova.qmd + - profile.qmd + - discrim.qmd + - cluster.qmd + - mapping.qmd + - princomp.qmd + - factor.qmd + - multiway.qmd + format: revealjs: df-print: paged diff --git a/ancova.html b/ancova.html index d0f986c..8162598 100644 --- a/ancova.html +++ b/ancova.html @@ -2641,10 +2641,10 @@

and then

Predictions (with interaction included), plotted

-
plot_cap(model = prepost.1, condition = c("before", "drug"))
+
plot_predictions(model = prepost.1, condition = c("before", "drug"))
-

Lines almost parallel, but not quite.

+

Lines almost parallel, but not quite.

Taking out interaction

@@ -2684,10 +2684,10 @@

Predictions

Plot of predicted values

-
plot_cap(prepost.2, condition = c("before", "drug"))
+
plot_predictions(prepost.2, condition = c("before", "drug"))
-

This time the lines are exactly parallel. No-interaction model forces them to have the same slope.

+

This time the lines are exactly parallel. No-interaction model forces them to have the same slope.

Different look at model output

diff --git a/ancova.pdf b/ancova.pdf index 55689fb..872887b 100644 Binary files a/ancova.pdf and b/ancova.pdf differ diff --git a/ancova.qmd b/ancova.qmd index d817495..48a7926 100644 --- a/ancova.qmd +++ b/ancova.qmd @@ -148,7 +148,7 @@ cbind(predictions(prepost.1, newdata = new)) %>% ## Predictions (with interaction included), plotted ```{r, fig.height=4} -plot_cap(model = prepost.1, condition = c("before", "drug")) +plot_predictions(model = prepost.1, condition = c("before", "drug")) ``` Lines almost parallel, but not quite. @@ -182,7 +182,7 @@ cbind(predictions(prepost.2, newdata = new)) %>% ## Plot of predicted values ```{r, fig.height=4} -plot_cap(prepost.2, condition = c("before", "drug")) +plot_predictions(prepost.2, condition = c("before", "drug")) ``` This time the lines are *exactly* parallel. No-interaction model forces them diff --git a/anova.pdf b/anova.pdf index b2b53d6..9b53ed8 100644 Binary files a/anova.pdf and b/anova.pdf differ diff --git a/asphalt.html b/asphalt.html index 719b492..f2cd9d6 100644 --- a/asphalt.html +++ b/asphalt.html @@ -2578,14 +2578,13 @@

Log of viscosity: more nearly linear?

ggplot(asphalt, aes(y = rut.depth, x = log(viscosity))) +
-  geom_point() + geom_smooth(se = F)
+ geom_point() + geom_smooth(se = F) -> g

(plot overleaf)

Rut depth against log-viscosity

- -
+

Comments and next steps

    @@ -2595,13 +2594,40 @@

    Comments and next steps

rut.1 <- lm(rut.depth ~ pct.a.surf + pct.a.base + fines +
-  voids + log(viscosity) + run, data = asphalt)
+ voids + log(viscosity) + run, data = asphalt) +summary(rut.1)
+
+

+Call:
+lm(formula = rut.depth ~ pct.a.surf + pct.a.base + fines + voids + 
+    log(viscosity) + run, data = asphalt)
+
+Residuals:
+    Min      1Q  Median      3Q     Max 
+-4.1211 -1.9075 -0.7175  1.6382  9.5947 
+
+Coefficients:
+               Estimate Std. Error t value Pr(>|t|)   
+(Intercept)    -12.9937    26.2188  -0.496   0.6247   
+pct.a.surf       3.9706     2.4966   1.590   0.1248   
+pct.a.base       1.2631     3.9703   0.318   0.7531   
+fines            0.1164     1.0124   0.115   0.9094   
+voids            0.5893     1.3244   0.445   0.6604   
+log(viscosity)  -3.1515     0.9194  -3.428   0.0022 **
+run             -1.9655     3.6472  -0.539   0.5949   
+---
+Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
+
+Residual standard error: 3.324 on 24 degrees of freedom
+Multiple R-squared:  0.806, Adjusted R-squared:  0.7575 
+F-statistic: 16.62 on 6 and 24 DF,  p-value: 1.743e-07
+

Regression output: summary(rut.1) or:

-
glance(rut.1)
+
glance(rut.1)
@@ -2610,7 +2636,7 @@

Regression output: summary(rut.1) or:

-
tidy(rut.1)
+
tidy(rut.1)
@@ -2624,20 +2650,18 @@

Regression output: summary(rut.1) or:

Comments

    -
  • R-squared 81%, not so bad.
  • -
  • P-value in glance asserts that something helping to predict rut.depth.
  • -
  • Table of coefficients says log(viscosity).
  • -
  • But confused by clearly non-significant variables: remove those to get clearer picture of what is helpful.
  • -
  • Before we do anything, look at residual plots: +
  • R-squared 81%, not so bad.

  • +
  • P-value in glance asserts that something helping to predict rut.depth.

  • +
  • Table of coefficients says log(viscosity).

  • +
  • But confused by clearly non-significant variables: remove those to get clearer picture of what is helpful.

  • +
  • Before we do anything, look at residual plots:

    +
    (a) of residuals against fitted values (as usual)
      -
      1. -
      2. of residuals against fitted values (as usual)
      3. -
      1. of residuals against each explanatory.
  • -
  • Problem fixes: +
  • Problem fixes:

    • with (a): fix response variable;
    • with some plots in (b): fix those explanatory variables.
    • @@ -2647,10 +2671,17 @@

      Comments

      Plot fitted values against residuals

      -
      ggplot(rut.1, aes(x = .fitted, y = .resid)) + geom_point()
      +
      ggplot(rut.1, aes(x = .fitted, y = .resid)) + geom_point()
      +
      +

      Normal quantile plot of residuals

      +
      +
      ggplot(rut.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()
      + +
      +

      Plotting residuals against \(x\) variables

        @@ -2658,13 +2689,13 @@

        Plotting residuals against \(x\) variables<
      • Package broom contains a function augment that combines these two together so that they can later be plotted: start with a model first, and then augment with a data frame:
      -
      rut.1 %>% augment(asphalt) -> rut.1a
      +
      rut.1 %>% augment(asphalt) -> rut.1a

      What does rut.1a contain?

      -
      names(rut.1a)
      +
      names(rut.1a)
       [1] "pct.a.surf" "pct.a.base" "fines"      "voids"      "rut.depth" 
        [6] "viscosity"  "run"        ".fitted"    ".resid"     ".hat"      
      @@ -2679,20 +2710,20 @@ 

      What does rut.1a contain?

      Plotting residuals against \(x\)-variables

      -
      rut.1a %>%
      -  mutate(log_vis=log(viscosity)) %>% 
      -  pivot_longer(
      -    c(pct.a.surf:voids, run, log_vis),
      -    names_to="xname", values_to="x"
      -  ) %>%
      -  ggplot(aes(x = x, y = .resid)) +
      -  geom_point() + facet_wrap(~xname, scales = "free") -> g
      +
      rut.1a %>%
      +  mutate(log_vis=log(viscosity)) %>% 
      +  pivot_longer(
      +    c(pct.a.surf:voids, run, log_vis),
      +    names_to="xname", values_to="x"
      +  ) %>%
      +  ggplot(aes(x = x, y = .resid)) +
      +  geom_point() + facet_wrap(~xname, scales = "free") -> g

      The plot

      -
      g
      +
      g
      @@ -2719,8 +2750,8 @@

      Which transformation?

      Running Box-Cox

      From package MASS:

      -
      boxcox(rut.depth ~ pct.a.surf + pct.a.base + fines + voids +
      -  log(viscosity) + run, data = asphalt)
      +
      boxcox(rut.depth ~ pct.a.surf + pct.a.base + fines + voids +
      +  log(viscosity) + run, data = asphalt)
      @@ -2745,20 +2776,20 @@

      Relationships with explanatories

    • As before: plot response (now log(rut.depth)) against other explanatory variables, all in one shot:
    -
    asphalt %>%
    -  mutate(log_vis=log(viscosity)) %>% 
    -  pivot_longer(
    -    c(pct.a.surf:voids, run, log_vis),
    -    names_to="xname", values_to="x"
    -  ) %>%
    -  ggplot(aes(y = log(rut.depth), x = x)) + geom_point() +
    -  facet_wrap(~xname, scales = "free") -> g3
    +
    asphalt %>%
    +  mutate(log_vis=log(viscosity)) %>% 
    +  pivot_longer(
    +    c(pct.a.surf:voids, run, log_vis),
    +    names_to="xname", values_to="x"
    +  ) %>%
    +  ggplot(aes(y = log(rut.depth), x = x)) + geom_point() +
    +  facet_wrap(~xname, scales = "free") -> g3

The new plots

-
g3
+
g3
@@ -2772,8 +2803,8 @@

Modelling with transformed response

  • Model log.rut.depth in terms of everything else, see what can be removed:
  • -
    rut.2 <- lm(log(rut.depth) ~ pct.a.surf + pct.a.base +
    -  fines + voids + log(viscosity) + run, data = asphalt)
    +
    rut.2 <- lm(log(rut.depth) ~ pct.a.surf + pct.a.base +
    +  fines + voids + log(viscosity) + run, data = asphalt)
    • use tidy from broom to display just the coefficients.
    • @@ -2782,7 +2813,7 @@

      Modelling with transformed response

      Output

      -
      tidy(rut.2)
      +
      tidy(rut.2)
      @@ -2791,6 +2822,33 @@

      Output

      +
      summary(rut.2)
      +
      +
      
      +Call:
      +lm(formula = log(rut.depth) ~ pct.a.surf + pct.a.base + fines + 
      +    voids + log(viscosity) + run, data = asphalt)
      +
      +Residuals:
      +     Min       1Q   Median       3Q      Max 
      +-0.53072 -0.18563 -0.00003  0.20017  0.55079 
      +
      +Coefficients:
      +               Estimate Std. Error t value Pr(>|t|)    
      +(Intercept)    -1.57299    2.43617  -0.646    0.525    
      +pct.a.surf      0.58358    0.23198   2.516    0.019 *  
      +pct.a.base     -0.10337    0.36891  -0.280    0.782    
      +fines           0.09775    0.09407   1.039    0.309    
      +voids           0.19885    0.12306   1.616    0.119    
      +log(viscosity) -0.55769    0.08543  -6.528 9.45e-07 ***
      +run             0.34005    0.33889   1.003    0.326    
      +---
      +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
      +
      +Residual standard error: 0.3088 on 24 degrees of freedom
      +Multiple R-squared:  0.961, Adjusted R-squared:  0.9512 
      +F-statistic: 98.47 on 6 and 24 DF,  p-value: 1.059e-15
      +
      @@ -2799,13 +2857,35 @@

      Taking out everything non-significant

    • Try: remove everything but pct.a.surf and log.viscosity:
    -
    rut.3 <- lm(log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)
    +
    rut.3 <- lm(log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)
    +summary(rut.3)
    +
    +
    
    +Call:
    +lm(formula = log(rut.depth) ~ pct.a.surf + log(viscosity), data = asphalt)
    +
    +Residuals:
    +     Min       1Q   Median       3Q      Max 
    +-0.61938 -0.21361  0.06635  0.14932  0.63012 
    +
    +Coefficients:
    +               Estimate Std. Error t value Pr(>|t|)    
    +(Intercept)     0.90014    1.08059   0.833   0.4119    
    +pct.a.surf      0.39115    0.21879   1.788   0.0846 .  
    +log(viscosity) -0.61856    0.02713 -22.797   <2e-16 ***
    +---
    +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 0.3208 on 28 degrees of freedom
    +Multiple R-squared:  0.9509,    Adjusted R-squared:  0.9474 
    +F-statistic: 270.9 on 2 and 28 DF,  p-value: < 2.2e-16
    +
    • Check that removing all those variables wasn’t too much:
    -
    anova(rut.3, rut.2)
    +
    anova(rut.3, rut.2)
    @@ -2823,7 +2903,7 @@

    Taking out everything non-significant

    Find the largest P-value by eye:

    -
    tidy(rut.2)
    +
    tidy(rut.2)
    @@ -2845,7 +2925,7 @@

    Get the computer to find the largest P-value for you

  • Output from tidy is itself a data frame, thus:
  • -
    tidy(rut.2) %>% arrange(p.value)
    +
    tidy(rut.2) %>% arrange(p.value)
    @@ -2865,14 +2945,14 @@

    Take out pct.a.base

  • Copy and paste the lm code and remove what you’re removing:
  • -
    rut.4 <- lm(log(rut.depth) ~ pct.a.surf + fines + voids + 
    -              log(viscosity) + run, data = asphalt)
    -tidy(rut.4) %>% arrange(p.value)
    +
    rut.4 <- lm(log(rut.depth) ~ pct.a.surf + fines + voids + 
    +              log(viscosity) + run, data = asphalt)
    +tidy(rut.4) %>% arrange(p.value) %>% dplyr::select(term, p.value)
    @@ -2885,8 +2965,8 @@

    Take out pct.a.base

    “Update”

    Another way to do the same thing:

    -
    rut.4 <- update(rut.2, . ~ . - pct.a.base)
    -tidy(rut.4) %>% arrange(p.value)
    +
    rut.4 <- update(rut.2, . ~ . - pct.a.base)
    +tidy(rut.4) %>% arrange(p.value)
    @@ -2903,13 +2983,13 @@

    “Update”

    Take out fines:

    -
    rut.5 <- update(rut.4, . ~ . - fines)
    -tidy(rut.5) %>% arrange(p.value)
    +
    rut.5 <- update(rut.4, . ~ . - fines)
    +tidy(rut.5) %>% arrange(p.value) %>% dplyr::select(term, p.value)
    @@ -2919,13 +2999,13 @@

    Take out fines:

    Take out run:

    -
    rut.6 <- update(rut.5, . ~ . - run)
    -tidy(rut.6) %>% arrange(p.value)
    +
    rut.6 <- update(rut.5, . ~ . - run)
    +tidy(rut.6) %>% arrange(p.value) %>% dplyr::select(term, p.value)
    @@ -2939,12 +3019,36 @@

    Comments

  • Different final result from taking things out one at a time (top), than by taking out 4 at once (bottom):
  • -
    coef(rut.6)
    +
    summary(rut.6)
    +
    +
    
    +Call:
    +lm(formula = log(rut.depth) ~ pct.a.surf + voids + log(viscosity), 
    +    data = asphalt)
    +
    +Residuals:
    +     Min       1Q   Median       3Q      Max 
    +-0.53548 -0.20181 -0.01702  0.16748  0.54707 
    +
    +Coefficients:
    +               Estimate Std. Error t value Pr(>|t|)    
    +(Intercept)    -1.02079    1.36430  -0.748   0.4608    
    +pct.a.surf      0.55547    0.22044   2.520   0.0180 *  
    +voids           0.24479    0.11560   2.118   0.0436 *  
    +log(viscosity) -0.64649    0.02879 -22.458   <2e-16 ***
    +---
    +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 0.3025 on 27 degrees of freedom
    +Multiple R-squared:  0.9579,    Adjusted R-squared:  0.9532 
    +F-statistic: 204.6 on 3 and 27 DF,  p-value: < 2.2e-16
    +
    +
    coef(rut.6)
       (Intercept)     pct.a.surf          voids log(viscosity) 
         -1.0207945      0.5554686      0.2447934     -0.6464911 
    -
    coef(rut.3)
    +
    coef(rut.3)
       (Intercept)     pct.a.surf log(viscosity) 
          0.9001389      0.3911481     -0.6185628 
    @@ -2963,7 +3067,7 @@

    Comments on variable selection

  • R has function step that does backward selection, like this:
  • -
    step(rut.2, direction = "backward", test = "F")
    +
    step(rut.2, direction = "backward", test = "F")

    Gets same answer as we did (by removing least significant x).

      @@ -2975,18 +3079,18 @@

      Comments on variable selection

      All possible regressions (output over)

      Uses package leaps:

      -
      leaps <- regsubsets(log(rut.depth) ~ pct.a.surf + 
      -                      pct.a.base + fines + voids + 
      -                      log(viscosity) + run, 
      -                    data = asphalt, nbest = 2)
      -s <- summary(leaps)
      -with(s, data.frame(rsq, outmat)) -> d
      +
      leaps <- regsubsets(log(rut.depth) ~ pct.a.surf + 
      +                      pct.a.base + fines + voids + 
      +                      log(viscosity) + run, 
      +                    data = asphalt, nbest = 2)
      +s <- summary(leaps)
      +with(s, data.frame(rsq, outmat)) -> d

    The output

    -
    d %>% rownames_to_column("model") %>% arrange(desc(rsq))
    +
    d %>% rownames_to_column("model") %>% arrange(desc(rsq))
    @@ -3008,9 +3112,9 @@

    Comments

    All possible regressions, adjusted R-squared

    -
    with(s, data.frame(adjr2, outmat)) %>% 
    -  rownames_to_column("model") %>% 
    -  arrange(desc(adjr2))
    +
    with(s, data.frame(adjr2, outmat)) %>% 
    +  rownames_to_column("model") %>% 
    +  arrange(desc(adjr2))
    @@ -3027,7 +3131,7 @@

    Revisiting the best model

  • Best model was our rut.6:
  • -
    tidy(rut.6)
    +
    tidy(rut.6)
    @@ -3045,37 +3149,45 @@

    Revisiting (2)

  • We should check residual plots again, though previous scatterplots say it’s unlikely that there will be a problem:
  • -
    g <- ggplot(rut.6, aes(y = .resid, x = .fitted)) + 
    -geom_point()
    +
    g <- ggplot(rut.6, aes(y = .resid, x = .fitted)) + 
    +geom_point()

    Residuals against fitted values

    -
    g
    - +
    g
    +
    +

    +
    +
    +
    +
    ggplot(rut.6, aes(sample = .resid)) + stat_qq() + stat_qq_line()
    +
    +

    -
    +
    +

    Plotting residuals against x’s

    • Do our trick again to put them all on one plot:
    -
    augment(rut.6, asphalt) %>%
    -  mutate(log_vis=log(viscosity)) %>% 
    -  pivot_longer(
    -    c(pct.a.surf:voids, run, log_vis),
    -    names_to="xname", values_to="x",
    -  ) %>%
    -  ggplot(aes(y = .resid, x = x)) + geom_point() +
    -  facet_wrap(~xname, scales = "free") -> g2
    +
    augment(rut.6, asphalt) %>%
    +  mutate(log_vis=log(viscosity)) %>% 
    +  pivot_longer(
    +    c(pct.a.surf:voids, run, log_vis),
    +    names_to="xname", values_to="x",
    +  ) %>%
    +  ggplot(aes(y = .resid, x = x)) + geom_point() +
    +  facet_wrap(~xname, scales = "free") -> g2

    Residuals against the x’s

    -
    g2
    +
    g2
    diff --git a/asphalt.pdf b/asphalt.pdf index b754cd2..3d90f84 100644 Binary files a/asphalt.pdf and b/asphalt.pdf differ diff --git a/bootstrap.pdf b/bootstrap.pdf index 13399da..bb4ea89 100644 Binary files a/bootstrap.pdf and b/bootstrap.pdf differ diff --git a/bootstrap_R.html b/bootstrap_R.html index 5a74637..ce79c11 100644 --- a/bootstrap_R.html +++ b/bootstrap_R.html @@ -2533,6 +2533,23 @@

    Blue Jays attendances

    [25] 30430
    +
      +
    • It is easier to see what is happening if we sort both the actual attendances and the bootstrap sample:
    • +
    +
    +
    sort(jays$attendance)
    +
    +
     [1] 14184 14433 15062 15086 15168 15606 16402 17264 17276 18581 19014 19217
    +[13] 21195 21312 21397 21519 29306 30430 33086 34743 37929 42419 42917 44794
    +[25] 48414
    +
    +
    sort(s)
    +
    +
     [1] 14433 15062 15062 15062 15086 15086 15168 16402 16402 17264 18581 19014
    +[13] 19014 19217 19217 21195 21195 21312 30430 34743 34743 34743 34743 44794
    +[25] 48414
    +
    +

    Getting mean of bootstrap sample

    @@ -2541,7 +2558,7 @@

    Getting mean of bootstrap sample

  • We need the mean of our bootstrap sample:
  • -
    mean(s)
    +
    mean(s)
    [1] 23055.28
    @@ -2550,7 +2567,7 @@

    Getting mean of bootstrap sample

  • This is a little different from the mean of our actual sample:
  • -
    mean(jays$attendance)
    +
    mean(jays$attendance)
    [1] 25070.16
    @@ -2566,7 +2583,7 @@

    Setting up bootstrap sampling

  • Begin by setting up a dataframe that contains a row for each bootstrap sample. I usually call this column sim. Do just 4 to get the idea:
  • -
    tibble(sim = 1:4)
    +
    tibble(sim = 1:4)
    @@ -2583,9 +2600,9 @@

    Drawing the bootstrap samples

  • Then set up to work one row at a time, and draw a bootstrap sample of the attendances in each row:
  • -
    tibble(sim = 1:4) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(jays$attendance, replace = TRUE)))
    +
    tibble(sim = 1:4) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(jays$attendance, replace = TRUE)))
    @@ -2605,10 +2622,10 @@

    Sample means

  • Find the mean of each sample:
  • -
    tibble(sim = 1:4) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% 
    -  mutate(my_mean = mean(sample))
    +
    tibble(sim = 1:4) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>%   
    +  mutate(my_mean = mean(sample))
    @@ -2628,17 +2645,17 @@

    Make a histogram of them

  • rather pointless here, but to get the idea:
  • -
    tibble(sim = 1:4) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% 
    -  mutate(my_mean = mean(sample)) %>% 
    -  ggplot(aes(x = my_mean)) + geom_histogram(bins = 3) -> g
    +
    tibble(sim = 1:4) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% 
    +  mutate(my_mean = mean(sample)) %>% 
    +  ggplot(aes(x = my_mean)) + geom_histogram(bins = 3) -> g

    The (pointless) histogram

    -
    g
    +
    g
    @@ -2648,17 +2665,17 @@

    Now do again with a decent number of bootstrap samples

  • say 1000, and put a decent number of bins on the histogram also:
  • -
    tibble(sim = 1:1000) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% 
    -  mutate(my_mean = mean(sample)) %>% 
    -  ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) -> g
    +
    tibble(sim = 1:1000) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% 
    +  mutate(my_mean = mean(sample)) %>% 
    +  ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) -> g

    The (better) histogram

    -
    g
    +
    g
    @@ -2679,9 +2696,9 @@

    Comments on the code 1/2

  • You might have been wondering about this:
  • -
    tibble(sim = 1:4) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(jays$attendance, replace = TRUE)))
    +
    tibble(sim = 1:4) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(jays$attendance, replace = TRUE)))
    @@ -2710,14 +2727,24 @@

    Comments on the code 2/2

    Two samples

    • Assumption: both samples are from a normal distribution.
    • -
    • In practice, each sample is “normal enough” given its sample size, since Central Limit Theorem will help.
    • +
    • In this case, each sample should be “normal enough” given its sample size, since Central Limit Theorem will help.
    • Use bootstrap on each group independently, as above.

    Kids learning to read

    -
    ggplot(kids, aes(x=group, y=score)) + geom_boxplot()
    +
    + +
    + +
    +
    +
    +
    +
    ggplot(kids, aes(x=group, y=score)) + geom_boxplot()
    @@ -2727,8 +2754,8 @@

    Getting just the control group

  • Use filter to select rows where something is true:
  • -
    kids %>% filter(group=="c") -> controls
    -controls
    +
    kids %>% filter(group=="c") -> controls
    +controls
    @@ -2742,23 +2769,23 @@

    Getting just the control group

    Bootstrap these

    -
    tibble(sim = 1:1000) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(controls$score, replace = TRUE))) %>% 
    -  mutate(my_mean = mean(sample)) %>% 
    -  ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) 
    +
    tibble(sim = 1:1000) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(controls$score, replace = TRUE))) %>% 
    +  mutate(my_mean = mean(sample)) %>% 
    +  ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) 

    … and the treatment group:

    -
    kids %>% filter(group=="t") -> treats
    -tibble(sim = 1:1000) %>% 
    -  rowwise() %>% 
    -  mutate(sample = list(sample(treats$score, replace = TRUE))) %>% 
    -  mutate(my_mean = mean(sample)) %>% 
    -  ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) 
    +
    kids %>% filter(group=="t") -> treats
    +tibble(sim = 1:1000) %>% 
    +  rowwise() %>% 
    +  mutate(sample = list(sample(treats$score, replace = TRUE))) %>% 
    +  mutate(my_mean = mean(sample)) %>% 
    +  ggplot(aes(x = my_mean)) + geom_histogram(bins = 10) 
    diff --git a/bootstrap_R.pdf b/bootstrap_R.pdf index dc49530..8a38f14 100644 Binary files a/bootstrap_R.pdf and b/bootstrap_R.pdf differ diff --git a/bootstrap_R.qmd b/bootstrap_R.qmd index 238e363..721c583 100644 --- a/bootstrap_R.qmd +++ b/bootstrap_R.qmd @@ -108,18 +108,10 @@ tibble(sim = 1:4) %>% ```{r bootstrap-R-8} tibble(sim = 1:4) %>% rowwise() %>% - mutate(sample = list(sample(jays$attendance, replace = TRU - - + mutate(sample = list(sample(jays$attendance, replace = TRUE))) %>% + mutate(my_mean = mean(sample)) ``` -```{r} - - -``` - -`{E))) %>%} mutate(my_mean = mean(sample))` - - These are (four simulated values of) the bootstrapped sampling distribution of the sample mean. diff --git a/choosing.html b/choosing.html index ab87ca7..521fb21 100644 --- a/choosing.html +++ b/choosing.html @@ -1,392 +1,2484 @@ - - - - + + + + + + + + + + Choosing things in dataframes - - + + + + + + + + + + + + + + resizeNewWidth = newWidth; + resizeNewHeight = newHeight; + + if (!resizePending) resizeDelayed(); + }; +}; + +var PagedTableDoc; +(function (PagedTableDoc) { + var allPagedTables = []; + + PagedTableDoc.initAll = function() { + allPagedTables = []; + + var pagedTables = [].slice.call(document.querySelectorAll('[data-pagedtable="false"],[data-pagedtable=""]')); + pagedTables.forEach(function(pagedTable, idx) { + pagedTable.setAttribute("data-pagedtable", "true"); + pagedTable.setAttribute("pagedtable-page", 0); + pagedTable.setAttribute("class", "pagedtable-wrapper"); + + var pagedTableInstance = new PagedTable(pagedTable); + pagedTableInstance.init(); + + allPagedTables.push(pagedTableInstance); + }); + }; + + PagedTableDoc.resizeAll = function() { + allPagedTables.forEach(function(pagedTable) { + pagedTable.render(); + }); + }; + + window.addEventListener("resize", PagedTableDoc.resizeAll); + + return PagedTableDoc; +})(PagedTableDoc || (PagedTableDoc = {})); + +window.onload = function() { + PagedTableDoc.initAll(); +}; + - +
    -
    +

    Choosing things in dataframes

    @@ -397,14 +2489,14 @@

    Choosing things in dataframes

    Packages

    The usual:

    -
    library(tidyverse)
    +
    library(tidyverse)

    Doing things with data frames

    Let’s go back to our Australian athletes:

    -
    athletes
    +
    athletes
    @@ -418,7 +2510,7 @@

    Doing things with data frames

    Choosing a column

    -
    athletes %>% select(Sport)
    +
    athletes %>% select(Sport)
    @@ -432,7 +2524,7 @@

    Choosing a column

    Choosing several columns

    -
    athletes %>% select(Sport, Hg, BMI)
    +
    athletes %>% select(Sport, Hg, BMI)
    @@ -446,7 +2538,7 @@

    Choosing several columns

    Choosing consecAutive columns

    -
    athletes %>% select(Sex:WCC)
    +
    athletes %>% select(Sex:WCC)
    @@ -460,7 +2552,7 @@

    Choosing consecAutive columns

    Choosing all-but some columns

    -
    athletes %>% select(-(RCC:LBM))
    +
    athletes %>% select(-(RCC:LBM))
    @@ -485,7 +2577,7 @@

    Select-helpers

    Columns whose names Abegin with S

    -
    athletes %>% select(starts_with("S"))
    +
    athletes %>% select(starts_with("S"))
    @@ -500,7 +2592,7 @@

    Columns whose names Abegin with S

    Columns whose names end with C

    either uppercase or lowercase:

    -
    athletes %>% select(ends_with("c"))
    +
    athletes %>% select(ends_with("c"))
    @@ -515,7 +2607,7 @@

    Columns whose names end with C

    Case-sensitive

    This works with any of the select-helpers:

    -
    athletes %>% select(ends_with("C", ignore.case=FALSE))
    +
    athletes %>% select(ends_with("C", ignore.case=FALSE))
    @@ -529,7 +2621,7 @@

    Case-sensitive

    Column names containing letter R

    -
    athletes %>% select(contains("r"))
    +
    athletes %>% select(contains("r"))
    @@ -549,7 +2641,7 @@

    Exactly two characters, ending with T

  • $ means “end of text”.
  • -
    athletes %>% select(matches("^.t$"))
    +
    athletes %>% select(matches("^.t$"))
    @@ -567,7 +2659,7 @@

    Choosing columns by property

  • eg, to choose text columns:
  • -
    athletes %>% select(where(is.character))
    +
    athletes %>% select(where(is.character))
    @@ -581,7 +2673,7 @@

    Choosing columns by property

    Choosing rows by number

    -
    athletes %>% slice(16:25)
    +
    athletes %>% slice(16:25)
    @@ -595,8 +2687,8 @@

    Choosing rows by number

    Non-consecutive rows

    -
    athletes %>% 
    -  slice(10, 13, 17, 42)
    +
    athletes %>% 
    +  slice(10, 13, 17, 42)
    @@ -610,7 +2702,7 @@

    Non-consecutive rows

    A random sample of rows

    -
    athletes %>% slice_sample(n=8)
    +
    athletes %>% slice_sample(n=8)
    @@ -624,7 +2716,7 @@

    A random sample of rows

    Rows for which something is true

    -
    athletes %>% filter(Sport == "Tennis")
    +
    athletes %>% filter(Sport == "Tennis")
    @@ -638,7 +2730,7 @@

    Rows for which something is true

    More complicated selections

    -
    athletes %>% filter(Sport == "Tennis", RCC < 5)
    +
    athletes %>% filter(Sport == "Tennis", RCC < 5)
    @@ -652,8 +2744,8 @@

    More complicated selections

    Another way to do “and”

    -
    athletes %>% filter(Sport == "Tennis") %>% 
    -  filter(RCC < 5)
    +
    athletes %>% filter(Sport == "Tennis") %>% 
    +  filter(RCC < 5)
    @@ -667,7 +2759,7 @@

    Another way to do “and”

    Either/Or

    -
    athletes %>% filter(Sport == "Tennis" | RCC > 5)
    +
    athletes %>% filter(Sport == "Tennis" | RCC > 5)
    @@ -681,7 +2773,7 @@

    Either/Or

    Sorting into order

    -
    athletes %>% arrange(RCC)
    +
    athletes %>% arrange(RCC)
    @@ -695,7 +2787,7 @@

    Sorting into order

    Breaking ties by another variable

    -
    athletes %>% arrange(RCC, BMI)
    +
    athletes %>% arrange(RCC, BMI)
    @@ -709,7 +2801,7 @@

    Breaking ties by another variable

    Descending order

    -
    athletes %>% arrange(desc(BMI))
    +
    athletes %>% arrange(desc(BMI))
    @@ -723,10 +2815,10 @@

    Descending order

    “The top ones”

    -
    athletes %>%
    +
    athletes %>%
       arrange(desc(Wt)) %>%
       slice(1:7) %>%
    -  select(Sport, Wt)
    + select(Sport, Wt)
    @@ -740,9 +2832,9 @@

    “The top ones”

    Another way

    -
    athletes %>% 
    +
    athletes %>% 
       slice_max(order_by = Wt, n=7) %>% 
    -  select(Sport, Wt)
    + select(Sport, Wt)
    @@ -756,10 +2848,10 @@

    Another way

    Create new variables from old ones

    -
    athletes %>%
    +
    athletes %>%
       mutate(wt_lb = Wt * 2.2) %>%
       select(Sport, Sex, Wt, wt_lb) %>% 
    -  arrange(Wt)
    + arrange(Wt)
    @@ -774,17 +2866,17 @@

    Create new variables from old ones

    Turning the result into a number

    Output is always data frame unless you explicitly turn it into something else, eg. the weight of the heaviest athlete, as a number:

    -
    athletes %>% arrange(desc(Wt)) %>% pluck("Wt", 1)
    +
    athletes %>% arrange(desc(Wt)) %>% pluck("Wt", 1)
    [1] 123.2

    Or the 20 heaviest weights in descending order:

    -
    athletes %>%
    +
    athletes %>%
       arrange(desc(Wt)) %>%
       slice(1:20) %>%
    -  pluck("Wt")
    + pluck("Wt")
     [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20  98.00  97.90  97.90
     [11]  97.00  96.90  96.30  94.80  94.80  94.70  94.70  94.60  94.25  94.20
    @@ -794,10 +2886,10 @@

    Turning the result into a number

    Another way to do the last one

    -
    athletes %>%
    +
    athletes %>%
       arrange(desc(Wt)) %>%
       slice(1:20) %>%
    -  pull("Wt")
    + pull("Wt")
     [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20  98.00  97.90  97.90
     [11]  97.00  96.90  96.30  94.80  94.80  94.70  94.70  94.60  94.25  94.20
    @@ -809,7 +2901,7 @@

    Another way to do the last one

    To find the mean height of the women athletes

    Two ways:

    -
    athletes %>% group_by(Sex) %>% summarize(m = mean(Ht))
    +
    athletes %>% group_by(Sex) %>% summarize(m = mean(Ht))
    @@ -820,9 +2912,9 @@

    To find the mean height of the women athletes

    -
    athletes %>%
    +
    athletes %>%
       filter(Sex == "female") %>%
    -  summarize(m = mean(Ht))
    + summarize(m = mean(Ht))
    @@ -834,11 +2926,11 @@

    To find the mean height of the women athletes

    -

    Summary of data selection/arrangement “verbs”

    +

    Summary of data selection/arrangement “verbs”

    --++ @@ -900,9 +2992,9 @@

    Looking things up in another data frame

  • Suppose you are working in the nails department of a hardware store and you find that you have sold these items:
  • -
    my_url <- "http://ritsokiguess.site/datafiles/nail_sales.csv"
    +
    my_url <- "http://ritsokiguess.site/datafiles/nail_sales.csv"
     sales <- read_csv(my_url)
    -sales
    +sales
    @@ -920,9 +3012,9 @@

    Product descriptions and prices

  • Fortunately you found a list of product descriptions and prices:

  • -
    my_url <- "http://ritsokiguess.site/datafiles/nail_desc.csv"
    +
    my_url <- "http://ritsokiguess.site/datafiles/nail_desc.csv"
     desc <- read_csv(my_url)
    -desc
    +desc
    @@ -933,7 +3025,7 @@

    Product descriptions and prices

      -
    • the size values are measured in inches (symbol "), but R uses the same symbol for the start and end of text, so the " representing “inches” is “escaped”. Hence the odd look.
    • +
    • the size values are measured in inches (symbol "), but R uses the same symbol for the start and end of text, so the " representing “inches” is “escaped”. Hence the odd look.
    @@ -943,7 +3035,7 @@

    The lookup

  • left_join.
  • -
    sales %>% left_join(desc)
    +
    sales %>% left_join(desc)
    @@ -963,9 +3055,9 @@

    What we have

    So now can work out how much the total revenue was:

    -
    sales %>% left_join(desc) %>% 
    +
    sales %>% left_join(desc) %>% 
       mutate(product_revenue = sales*price) %>% 
    -  summarize(total_revenue = sum(product_revenue))
    + summarize(total_revenue = sum(product_revenue))
    @@ -994,8 +3086,8 @@

    Matching on only some matching names

  • Suppose the sales dataframe also had a column qty (which was the quantity sold):
  • -
    sales %>% rename("qty"="sales") -> sales1
    -sales1
    +
    sales %>% rename("qty"="sales") -> sales1
    +sales1
    @@ -1012,8 +3104,8 @@

    Matching on only some matching names

    Matching only on product code

    -
    sales1 %>% 
    -  left_join(desc, join_by(product_code))
    +
    sales1 %>% 
    +  left_join(desc, join_by(product_code))
    @@ -1024,8 +3116,8 @@

    Matching only on product code

    -
    sales1 %>% 
    -  left_join(desc)
    +
    sales1 %>% 
    +  left_join(desc)
    @@ -1045,8 +3137,8 @@

    Matching on different names 1/2

  • Suppose the product code in sales was just code:
  • -
    sales %>% rename("code" = "product_code") -> sales2
    -sales2
    +
    sales %>% rename("code" = "product_code") -> sales2
    +sales2
    @@ -1066,8 +3158,8 @@

    Matching on different names 2/2

  • Use join_by, but like this:
  • -
    sales2 %>% 
    -  left_join(desc, join_by(code == product_code))
    +
    sales2 %>% 
    +  left_join(desc, join_by(code == product_code))
    @@ -1089,7 +3181,7 @@

    Other types of join

    Full join here

    -
    sales %>% full_join(desc)
    +
    sales %>% full_join(desc)
    @@ -1107,7 +3199,7 @@

    Full join here

    The same thing, but with anti_join

    Anything in first df but not in second?

    -
    desc %>% anti_join(sales)
    +
    desc %>% anti_join(sales)
    @@ -1116,7 +3208,7 @@

    The same thing, but with anti_join

    -
    sales %>% anti_join(desc)
    +
    sales %>% anti_join(desc)
    @@ -1135,26 +3227,854 @@

    The same thing, but with anti_join

    - + - - - - - + + + + + - - - - + + + + - - - + + + \ No newline at end of file diff --git a/choosing.pdf b/choosing.pdf index 360f1ae..c47883e 100644 Binary files a/choosing.pdf and b/choosing.pdf differ diff --git a/choosing_files/execute-results/html.json b/choosing_files/execute-results/html.json deleted file mode 100644 index b1a18ad..0000000 --- a/choosing_files/execute-results/html.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "hash": "8a1b88b16954fb13b78c31ee298ba634", - "result": { - "markdown": "---\ntitle: \"Choosing things in dataframes\"\n---\n\n\n## Packages\n\nThe usual:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n## Doing things with data frames\n\nLet's go back to our Australian athletes:\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Choosing a column\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Choosing several columns\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport, Hg, BMI)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Choosing consecAutive columns\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sex:WCC)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Choosing all-but some columns\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(-(RCC:LBM))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Select-helpers\n\nOther ways to select columns: those whose name:\n\n- `starts_with` something\n- `ends_with` something\n- `contains` something\n- `matches` a \"regular expression\"\n- `everything()` select all the columns\n\n## Columns whose names Abegin with S\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(starts_with(\"S\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Columns whose names end with C\n\neither uppercase or lowercase:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"c\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Case-sensitive\n\nThis works with any of the select-helpers:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"C\", ignore.case=FALSE))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Column names containing letter R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(contains(\"r\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Exactly two characters, ending with T\n\nIn regular expression terms, this is `^.t$`:\n\n- `^` means \"start of text\"\n- `.` means \"exactly one character, but could be anything\"\n- `$` means \"end of text\".\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(matches(\"^.t$\"))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Choosing columns by property\n\n- Use `where` as with summarizing several columns\n- eg, to choose text columns:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(where(is.character))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Choosing rows by number\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice(16:25)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Non-consecutive rows\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice(10, 13, 17, 42)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## A random sample of rows\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice_sample(n=8)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Rows for which something is true\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\")\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## More complicated selections\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\", RCC < 5)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Another way to do \"and\"\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\") %>% \n filter(RCC < 5)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Either/Or\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\" | RCC > 5)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Sorting into order\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Breaking ties by another variable\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC, BMI)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Descending order\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(BMI))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## \"The top ones\"\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:7) %>%\n select(Sport, Wt)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Another way\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice_max(order_by = Wt, n=7) %>% \n select(Sport, Wt)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Create new variables from old ones\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n mutate(wt_lb = Wt * 2.2) %>%\n select(Sport, Sex, Wt, wt_lb) %>% \n arrange(Wt)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Turning the result into a number\n\nOutput is always data frame unless you explicitly turn it into something\nelse, eg. the weight of the heaviest athlete, as a number:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(Wt)) %>% pluck(\"Wt\", 1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.2\n```\n:::\n:::\n\n\nOr the 20 heaviest weights in descending order:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pluck(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90 97.90\n[11] 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60 94.25 94.20\n```\n:::\n:::\n\n\n## Another way to do the last one\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pull(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90 97.90\n[11] 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60 94.25 94.20\n```\n:::\n:::\n\n\n`pull` grabs the column you name *as a vector* (of whatever it\ncontains).\n\n## To find the mean height of the women athletes\n\nTwo ways:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% group_by(Sex) %>% summarize(m = mean(Ht))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n filter(Sex == \"female\") %>%\n summarize(m = mean(Ht))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n\\normalsize\n\n## Summary of data selection/arrangement \"verbs\" {.smaller}\n\n| Verb | Purpose |\n|:---------------|:-------------------------------------------------------|\n| `select` | Choose columns |\n| `slice` | Choose rows by number |\n| `slice_sample` | Choose random rows |\n| `slice_max` | Choose rows with largest values on a variable (also `slice_min`) |\n| `filter` | Choose rows satisfying conditions |\n| `arrange` | Sort in order by column(s) |\n| `mutate` | Create new variables |\n| `group_by` | Create groups to work with |\n| `summarize` | Calculate summary statistics (by groups if defined) |\n| `pluck` | Extract items from data frame |\n| `pull` | Extract a single column from a data frame as a vector |\n\n## Looking things up in another data frame\n\n- Suppose you are working in the nails department of a hardware store\n and you find that you have sold these items:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_sales.csv\"\nsales <- read_csv(my_url)\nsales\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Product descriptions and prices\n\n- but you don't remember what these product codes are, and you would\n like to know the total revenue from these sales.\n\n- Fortunately you found a list of product descriptions and prices:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_desc.csv\"\ndesc <- read_csv(my_url)\ndesc\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n- the `size` values are measured in inches (symbol `\"`), but R uses\n the same symbol for the start and end of text, so the `\"`\n representing \"inches\" is \"escaped\". Hence the odd look.\n\n\\normalsize\n\n## The lookup\n\n- How do you \"look up\" the product codes to find the product\n descriptions and prices?\n- `left_join`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## What we have\n\n- this looks up all the rows in the *first* dataframe that are also in\n the *second*.\n- by default matches all columns with same name in two dataframes\n (`product_code` here)\n- get *all* columns in *both* dataframes. The rows are the ones for\n that `product_code`.\n\nSo now can work out how much the total revenue was:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc) %>% \n mutate(product_revenue = sales*price) %>% \n summarize(total_revenue = sum(product_revenue))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## More comments\n\n- if any product codes are not matched, you get NA in the added\n columns\n- anything in the *second* dataframe that was not in the first does\n not appear (here, any products that were not sold)\n- other variations (examples follow):\n - if there are two columns with the same name in the two\n dataframes, and you only want to match on one, use `by` with one\n column name\n - if the columns you want to look up have different names in the\n two dataframes, use `by` with a \"named list\"\n\n## Matching on only some matching names\n\n- Suppose the `sales` dataframe *also* had a column `qty` (which was\n the quantity sold):\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"qty\"=\"sales\") -> sales1\nsales1\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n\\normalsize\n\n- The `qty` in `sales1` is the quantity sold, but the `qty` in `desc`\n is the number of nails in a package. These should *not* be matched:\n they are different things.\n\n## Matching only on product code\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales1 %>% \n left_join(desc, join_by(product_code))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nsales1 %>% \n left_join(desc)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n- Get `qty.x` (from `sales1`) and `qty.y` (from `desc`).\n\n## Matching on different names 1/2\n\n- Suppose the product code in `sales` was just `code`:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"code\" = \"product_code\") -> sales2\nsales2\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n\\normalsize\n\n- How to match the two product codes that have different names?\n\n## Matching on different names 2/2\n\n- Use `join_by`, but like this:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales2 %>% \n left_join(desc, join_by(code == product_code))\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n## Other types of join\n\n- `right_join`: interchanges roles, looking up keys from second\n dataframe in first.\n- `anti_join`: give me all the rows in the first dataframe that are\n *not* in the second. (Use this eg. to see whether the product\n descriptions are incomplete.)\n- `full_join`: give me all the rows in both dataframes, with missings\n as needed.\n\n## Full join here\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% full_join(desc)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\n\\normalsize\n\n- The missing `sales` for \"masonry nail\" says that it was in the\n lookup table `desc`, but we didn't sell any.\n\n## The same thing, but with `anti_join`\n\nAnything in first df but not in second?\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesc %>% anti_join(sales)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n\n```{.r .cell-code}\nsales %>% anti_join(desc)\n```\n\n::: {.cell-output-display}\n`````{=html}\n
    \n \n
    \n`````\n:::\n:::\n\n\nMasonry nails are the only thing in our product description file that we\ndid not sell any of.\n", - "supporting": [ - "choosing_files" - ], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": { - "include-in-header": [ - "\n\n" - ], - "include-after-body": [ - "\n\n\n" - ] - }, - "engineDependencies": {}, - "preserve": {}, - "postProcess": true - } -} \ No newline at end of file diff --git a/choosing_files/execute-results/tex.json b/choosing_files/execute-results/tex.json deleted file mode 100644 index b6df1ec..0000000 --- a/choosing_files/execute-results/tex.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "hash": "1a27c83e2f5ea4aee31a32c65b194163", - "result": { - "markdown": "---\ntitle: \"Choosing things in dataframes\"\n---\n\n\n\n## Packages\n\nThe usual:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(tidyverse)\n```\n:::\n\n\n\n\n## Doing things with data frames\nLet’s go back to our Australian athletes: \n\n\n\n::: {.cell}\n\n:::\n\n\n\n\\scriptsize\n\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 4.56 13.3 42.2 13.6 20 19.2 49 11.3 53.1\n 2 female Netba~ 4.15 6 38 12.7 59 21.2 110. 25.3 47.1\n 3 female Netba~ 4.16 7.6 37.5 12.3 22 21.4 89 19.4 53.4\n 4 female Netba~ 4.32 6.4 37.7 12.3 30 21.0 98.3 19.6 48.8\n 5 female Netba~ 4.06 5.8 38.7 12.8 78 21.8 122. 23.1 56.0\n 6 female Netba~ 4.12 6.1 36.6 11.8 21 21.4 90.4 16.9 56.4\n 7 female Netba~ 4.17 5 37.4 12.7 109 21.5 107. 21.3 53.1\n 8 female Netba~ 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4\n 9 female Netba~ 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0\n10 female Netba~ 4.44 9.7 41.4 14.1 64 22.8 126. 25.0 51.6\n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n## Choosing a column\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 1\n Sport \n \n 1 Netball\n 2 Netball\n 3 Netball\n 4 Netball\n 5 Netball\n 6 Netball\n 7 Netball\n 8 Netball\n 9 Netball\n10 Netball\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing several columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sport, Hg, BMI)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sport Hg BMI\n \n 1 Netball 13.6 19.2\n 2 Netball 12.7 21.2\n 3 Netball 12.3 21.4\n 4 Netball 12.3 21.0\n 5 Netball 12.8 21.8\n 6 Netball 11.8 21.4\n 7 Netball 12.7 21.5\n 8 Netball 12.4 24.4\n 9 Netball 12.4 22.6\n10 Netball 14.1 22.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing consecutive columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(Sex:WCC)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sex Sport RCC WCC\n \n 1 female Netball 4.56 13.3\n 2 female Netball 4.15 6 \n 3 female Netball 4.16 7.6\n 4 female Netball 4.32 6.4\n 5 female Netball 4.06 5.8\n 6 female Netball 4.12 6.1\n 7 female Netball 4.17 5 \n 8 female Netball 3.8 6.6\n 9 female Netball 3.96 5.5\n10 female Netball 4.44 9.7\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing all-but some columns\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(-(RCC:LBM))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sex Sport Ht Wt\n \n 1 female Netball 177. 59.9\n 2 female Netball 173. 63 \n 3 female Netball 176 66.3\n 4 female Netball 170. 60.7\n 5 female Netball 183 72.9\n 6 female Netball 178. 67.9\n 7 female Netball 177. 67.5\n 8 female Netball 174. 74.1\n 9 female Netball 174. 68.2\n10 female Netball 174. 68.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Select-helpers\nOther ways to select columns: those whose name:\n\n- `starts_with` something\n- `ends_with` something\n- `contains` something\n- `matches` a “regular expression”\n- `everything()` select all the columns\n\n## Columns whose names begin with S \n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(starts_with(\"S\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sex Sport SSF\n \n 1 female Netball 49 \n 2 female Netball 110. \n 3 female Netball 89 \n 4 female Netball 98.3\n 5 female Netball 122. \n 6 female Netball 90.4\n 7 female Netball 107. \n 8 female Netball 157. \n 9 female Netball 101. \n10 female Netball 126. \n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Columns whose names end with C\n\neither uppercase or lowercase:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"c\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n RCC WCC Hc\n \n 1 4.56 13.3 42.2\n 2 4.15 6 38 \n 3 4.16 7.6 37.5\n 4 4.32 6.4 37.7\n 5 4.06 5.8 38.7\n 6 4.12 6.1 36.6\n 7 4.17 5 37.4\n 8 3.8 6.6 36.5\n 9 3.96 5.5 36.3\n10 4.44 9.7 41.4\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Case-sensitive\n\nThis works with any of the select-helpers:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(ends_with(\"C\", ignore.case=FALSE))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n RCC WCC\n \n 1 4.56 13.3\n 2 4.15 6 \n 3 4.16 7.6\n 4 4.32 6.4\n 5 4.06 5.8\n 6 4.12 6.1\n 7 4.17 5 \n 8 3.8 6.6\n 9 3.96 5.5\n10 4.44 9.7\n# i 192 more rows\n```\n:::\n:::\n\n\n\n\n## Column names containing letter R\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(contains(\"r\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 3\n Sport RCC Ferr\n \n 1 Netball 4.56 20\n 2 Netball 4.15 59\n 3 Netball 4.16 22\n 4 Netball 4.32 30\n 5 Netball 4.06 78\n 6 Netball 4.12 21\n 7 Netball 4.17 109\n 8 Netball 3.8 102\n 9 Netball 3.96 71\n10 Netball 4.44 64\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Exactly two characters, ending with T\n\nIn regular expression terms, this is `^.t$`:\n\n- `^` means “start of text”\n- `.` means “exactly one character, but could be anything”\n- `$` means “end of text”.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(matches(\"^.t$\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n Ht Wt\n \n 1 177. 59.9\n 2 173. 63 \n 3 176 66.3\n 4 170. 60.7\n 5 183 72.9\n 6 178. 67.9\n 7 177. 67.5\n 8 174. 74.1\n 9 174. 68.2\n10 174. 68.8\n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Choosing columns by property\n\n- Use `where` as with summarizing several columns\n- eg, to choose text columns:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% select(where(is.character))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 2\n Sex Sport \n \n 1 female Netball\n 2 female Netball\n 3 female Netball\n 4 female Netball\n 5 female Netball\n 6 female Netball\n 7 female Netball\n 8 female Netball\n 9 female Netball\n10 female Netball\n# i 192 more rows\n```\n:::\n:::\n\n\n\n\n## Choosing rows by number \n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice(16:25)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 10 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 4.25 10.7 39.5 13.2 127 24.5 157. 26.5 54.5\n 2 female Netba~ 4.46 10.9 39.7 13.7 102 24.0 116. 23.0 57.2\n 3 female Netba~ 4.4 9.3 40.4 13.6 86 26.2 182. 30.1 54.4\n 4 female Netba~ 4.83 8.4 41.8 13.4 40 20.0 71.6 13.9 57.6\n 5 female Netba~ 4.23 6.9 38.3 12.6 50 25.7 144. 26.6 61.5\n 6 female Netba~ 4.24 8.4 37.6 12.5 58 25.6 201. 35.5 53.5\n 7 female Netba~ 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1\n 8 female Netba~ 4.03 8.5 37.7 13 51 23.4 104. 19.6 55.4\n 9 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3\n10 female BBall 4.41 8.3 38.2 12.7 68 20.7 103. 21.3 58.6\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n\n\n## Non-consecutive rows \n\n\\tiny\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice(10, 13, 17, 42)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 4 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Netball 4.44 9.7 41.4 14.1 64 22.8 126. 25.0 51.6\n2 female Netball 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3\n3 female Netball 4.46 10.9 39.7 13.7 102 24.0 116. 23.0 57.2\n4 female Row 4.37 8.1 41.8 14.3 53 23.5 98 21.8 63.0\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n\\normalsize\n\n## A random sample of rows\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% slice_sample(n=8)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 8 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Row 4.87 6.4 44.8 15 64 20.2 99.8 20.1 52.7\n2 male Tennis 5.66 8.3 50.2 17.7 38 23.8 56.5 10.0 72 \n3 male T400m 4.55 5.55 42.6 14.4 106 21.2 34.1 6.06 57 \n4 female BBall 4.35 7.8 41.4 14.1 30 22.0 118. 23.3 48.3\n5 male Row 5.22 6 46.6 15.7 72 25.1 43.1 7.49 83 \n6 male WPolo 4.63 14.3 44.8 15 133 25.4 49.5 8.97 79 \n7 male WPolo 4.91 10.2 45 15.2 234 23.7 56.5 10.1 68 \n8 male Row 5.22 8.4 47.5 16.2 89 25.3 44.5 9.36 79 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n## Rows for which something is true\n\n\\tiny\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 11 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n 2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n 3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n 4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n 5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n 6 female Tennis 5.16 7.2 44.3 14.5 88 18.3 61.9 12.9 48.8\n 7 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9\n 8 male Tennis 5.66 8.3 50.2 17.7 38 23.8 56.5 10.0 72 \n 9 male Tennis 5.03 6.4 42.7 14.3 122 22.0 47.6 8.51 68 \n10 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 \n11 male Tennis 5.38 6.3 46 15.7 32 21.1 34.9 6.26 72 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\\normalsize\n\n## More complicated selections\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\", RCC < 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n6 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9\n7 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Another way to do \"and\"\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\") %>% \n filter(RCC < 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n1 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n2 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n3 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n4 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n5 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n6 female Tennis 4.66 6.4 40.9 13.9 109 18.4 38.2 8.45 41.9\n7 male Tennis 4.97 8.8 43 14.9 233 22.3 60.4 11.5 63 \n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n\n## Either/Or\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% filter(Sport == \"Tennis\" | RCC > 5)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 66 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Row 5.02 6.4 44.8 15.2 48 19.8 91 19.2 53.6\n 2 female T400m 5.31 9.5 47.1 15.9 29 21.4 57.9 11.1 57.5\n 3 female Field 5.33 9.3 47 15 62 25.3 103. 19.5 59.9\n 4 female TSprnt 5.16 8.2 45.3 14.7 34 20.3 46.1 10.2 51.5\n 5 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n 6 female Tennis 4.4 4 40.8 13.9 73 22.1 98.1 19.6 56.0\n 7 female Tennis 4.38 7.9 39.8 13.5 88 21.2 80.6 17.1 46.5\n 8 female Tennis 4.08 6.6 37.8 12.1 182 20.5 68.3 15.3 51.8\n 9 female Tennis 4.98 6.4 44.8 14.8 80 17.1 47.6 11.1 42.2\n10 female Tennis 5.16 7.2 44.3 14.5 88 18.3 61.9 12.9 48.8\n# i 56 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Sorting into order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4\n 2 female Netba~ 3.9 6.3 35.9 12.1 78 20.1 70 15.0 57.3\n 3 female T400m 3.9 6 38.9 13.5 16 19.4 48.4 10.5 53.7\n 4 female Row 3.91 7.3 37.6 12.9 43 22.3 126. 25.2 54.8\n 5 female Netba~ 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1\n 6 female Row 3.95 3.3 36.9 12.5 40 24.5 74.9 16.4 63.0\n 7 female Netba~ 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0\n 8 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3\n 9 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n10 female Netba~ 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3\n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Breaking ties by another variable\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(RCC, BMI)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 female Netba~ 3.8 6.6 36.5 12.4 102 24.4 157. 26.6 54.4\n 2 female T400m 3.9 6 38.9 13.5 16 19.4 48.4 10.5 53.7\n 3 female Netba~ 3.9 6.3 35.9 12.1 78 20.1 70 15.0 57.3\n 4 female Row 3.91 7.3 37.6 12.9 43 22.3 126. 25.2 54.8\n 5 female Netba~ 3.95 6.6 38.4 12.8 33 19.9 68.9 15.6 54.1\n 6 female Row 3.95 3.3 36.9 12.5 40 24.5 74.9 16.4 63.0\n 7 female BBall 3.96 7.5 37.5 12.3 60 20.6 109. 19.8 63.3\n 8 female Netba~ 3.96 5.5 36.3 12.4 71 22.6 101. 17.9 56.0\n 9 female Tennis 4 4.2 36.6 12 57 25.4 109 20.9 56.6\n10 female Netba~ 4.02 9.1 37.7 12.7 107 23.0 77 18.1 57.3\n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## Descending order\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(BMI))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 13\n Sex Sport RCC WCC Hc Hg Ferr BMI SSF `%Bfat` LBM\n \n 1 male Field 5.48 6.2 48.2 16.3 94 34.4 82.7 13.9 106 \n 2 male Field 4.96 8.3 45.3 15.7 141 33.7 114. 17.4 89 \n 3 male Field 5.48 4.6 49.4 18 132 32.5 55.7 8.51 102 \n 4 female Field 4.75 7.5 43.8 15.2 90 31.9 132. 23.0 73.0\n 5 male Field 5.01 8.9 46 15.9 212 30.2 112. 19.9 78 \n 6 male Field 5.01 8.9 46 15.9 212 30.2 96.9 18.1 80 \n 7 male Field 5.09 8.9 46.3 15.4 44 30.0 71.1 14.0 88 \n 8 female Field 4.58 5.8 42.1 14.7 164 28.6 110. 21.3 68.9\n 9 female Field 4.51 9 39.7 14.3 36 28.1 136. 24.9 63.0\n10 male WPolo 5.34 6.2 49.8 17.2 143 27.8 75.7 13.5 82 \n# i 192 more rows\n# i 2 more variables: Ht , Wt \n```\n:::\n:::\n\n\n\n## “The top ones”\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:7) %>%\n select(Sport, Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n Sport Wt\n \n1 Field 123.\n2 BBall 114.\n3 Field 111.\n4 Field 108.\n5 Field 103.\n6 WPolo 101 \n7 BBall 100.\n```\n:::\n:::\n\n\n\n## Another way\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% \n slice_max(order_by = Wt, n=7) %>% \n select(Sport, Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 2\n Sport Wt\n \n1 Field 123.\n2 BBall 114.\n3 Field 111.\n4 Field 108.\n5 Field 103.\n6 WPolo 101 \n7 BBall 100.\n```\n:::\n:::\n\n\n\n\n## Create new variables from old ones\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n mutate(wt_lb = Wt * 2.2) %>%\n select(Sport, Sex, Wt, wt_lb) %>% \n arrange(Wt)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 202 x 4\n Sport Sex Wt wt_lb\n \n 1 Gym female 37.8 83.2\n 2 Gym female 43.8 96.4\n 3 Gym female 45.1 99.2\n 4 Tennis female 45.8 101. \n 5 Tennis female 47.4 104. \n 6 Gym female 47.8 105. \n 7 T400m female 49.2 108. \n 8 Row female 49.8 110. \n 9 T400m female 50.9 112. \n10 Netball female 51.9 114. \n# i 192 more rows\n```\n:::\n:::\n\n\n\n## Turning the result into a number\nOutput is always data frame unless you explicitly turn it into something\nelse, eg. the weight of the heaviest athlete, as a number:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% arrange(desc(Wt)) %>% pluck(\"Wt\", 1)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n[1] 123.2\n```\n:::\n:::\n\n\n\nOr the 20 heaviest weights in descending order:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pluck(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90\n[10] 97.90 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60\n[19] 94.25 94.20\n```\n:::\n:::\n\n\n\n## Another way to do the last one\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n arrange(desc(Wt)) %>%\n slice(1:20) %>%\n pull(\"Wt\")\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] 123.20 113.70 111.30 108.20 102.70 101.00 100.20 98.00 97.90\n[10] 97.90 97.00 96.90 96.30 94.80 94.80 94.70 94.70 94.60\n[19] 94.25 94.20\n```\n:::\n:::\n\n\n\n`pull` grabs the column you name *as a vector* (of whatever it contains).\n\n## To find the mean height of the women athletes\nTwo ways:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>% group_by(Sex) %>% summarize(m = mean(Ht))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 2 x 2\n Sex m\n \n1 female 175.\n2 male 186.\n```\n:::\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nathletes %>%\n filter(Sex == \"female\") %>%\n summarize(m = mean(Ht))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n m\n \n1 175.\n```\n:::\n:::\n\n\n\n\\normalsize\n\n## Summary of data selection/arrangement \"verbs\" {.smaller}\n\n | Verb | Purpose|\n |:-----|:-------------------------------|\n |`select` | Choose columns|\n |`slice` | Choose rows by number|\n |`slice_sample` | Choose random rows |\n |`slice_max` | Choose rows with largest values on a variable (also `slice_min`) |\n |`filter` | Choose rows satisfying conditions|\n | `arrange` | Sort in order by column(s) |\n | `mutate` | Create new variables | \n | `group_by` | Create groups to work with|\n |`summarize` | Calculate summary statistics (by groups if defined) |\n | `pluck` | Extract items from data frame |\n | `pull` | Extract a single column from a data frame as a vector|\n\n \n## Looking things up in another data frame\n\n- Suppose you are working in the nails department of a hardware store and you find that you have sold these items:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_sales.csv\"\nsales <- read_csv(my_url)\nsales\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n product_code sales\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\n\n## Product descriptions and prices\n\n- but you don't remember what these product codes are, and you would like to know the total revenue from these sales.\n\n- Fortunately you found a list of product descriptions and prices:\n\n\\small\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_url <- \"http://ritsokiguess.site/datafiles/nail_desc.csv\"\ndesc <- read_csv(my_url)\ndesc\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 5\n product_code description size qty price\n \n1 061-4525-2 spike nail \"10\\\"\" 1 1.49\n2 061-5329-4 masonry nail \"1.5\\\"\" 112 8.19\n3 061-5344-6 finishing nail \"1\\\"\" 1298 6.99\n4 061-5375-2 roofing nail \"1.25\\\"\" 192 6.99\n5 061-5388-2 framing nail \"4\\\"\" 25 8.19\n6 161-0090-0 wood nail \"1\\\"\" 25 2.39\n7 161-0199-4 panel nail \"1-5/8\\\"\" 20 4.69\n```\n:::\n:::\n\n\n\n- the `size` values are measured in inches (symbol `\"`), but R uses the same symbol for the start and end of text, so the `\"` representing \"inches\" is \"escaped\". Hence the odd look.\n\n\\normalsize\n\n## The lookup\n\n- How do you \"look up\" the product codes to find the product descriptions and prices?\n- `left_join`.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n product_code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n## What we have\n\n- this looks up all the rows in the *first* dataframe that are also in the *second*. \n- by default matches all columns with same name in two dataframes (`product_code` here)\n- get *all* columns in *both* dataframes. The rows are the ones for that `product_code`.\n\nSo now can work out how much the total revenue was:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% left_join(desc) %>% \n mutate(product_revenue = sales*price) %>% \n summarize(total_revenue = sum(product_revenue))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 1\n total_revenue\n \n1 178.\n```\n:::\n:::\n\n\n\n\n## More comments\n\n- if any product codes are not matched, you get NA in the added columns\n- anything in the *second* dataframe that was not in the first does not appear (here, any products that were not sold)\n- other variations (examples follow):\n - if there are two columns with the same name in the two dataframes, and you only want to match on one, use `by` with one column name\n - if the columns you want to look up have different names in the two dataframes, use `by` with a \"named list\"\n\n## Matching on only some matching names\n\n- Suppose the `sales` dataframe *also* had a column `qty` (which was the quantity sold): \n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"qty\"=\"sales\") -> sales1\nsales1\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n product_code qty\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\\normalsize\n\n- The `qty` in `sales1` is the quantity sold, but the `qty` in `desc` is the number of nails in a package. These should *not* be matched: they are different things.\n\n## Matching only on product code\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales1 %>% \n left_join(desc, join_by(product_code))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n product_code qty.x description size qty.y price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n- Get `qty.x` (from `sales1`) and `qty.y` (from `desc`).\n\n## Matching on different names 1/2\n\n- Suppose the product code in `sales` was just `code`:\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% rename(\"code\" = \"product_code\") -> sales2\nsales2\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 2\n code sales\n \n1 061-5344-6 10\n2 161-0090-0 6\n3 061-5388-2 2\n4 161-0199-4 8\n5 061-5375-2 5\n6 061-4525-2 3\n```\n:::\n:::\n\n\n\\normalsize\n\n- How to match the two product codes that have different names?\n\n## Matching on different names 2/2\n\n- Use `by`, but like this:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales2 %>% \n left_join(desc, join_by(code == product_code))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 6 x 6\n code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n```\n:::\n:::\n\n\n\n## Other types of join\n\n- `right_join`: interchanges roles, looking up keys from second dataframe in first.\n- `anti_join`: give me all the rows in the first dataframe that are *not* in the second. (Use this eg. to see whether the product descriptions are incomplete.)\n- `full_join`: give me all the rows in both dataframes, with missings as needed.\n\n## Full join here\n\n\\small\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsales %>% full_join(desc)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 7 x 6\n product_code sales description size qty price\n \n1 061-5344-6 10 finishing nail \"1\\\"\" 1298 6.99\n2 161-0090-0 6 wood nail \"1\\\"\" 25 2.39\n3 061-5388-2 2 framing nail \"4\\\"\" 25 8.19\n4 161-0199-4 8 panel nail \"1-5/8\\\"\" 20 4.69\n5 061-5375-2 5 roofing nail \"1.25\\\"\" 192 6.99\n6 061-4525-2 3 spike nail \"10\\\"\" 1 1.49\n7 061-5329-4 NA masonry nail \"1.5\\\"\" 112 8.19\n```\n:::\n:::\n\n\n\\normalsize\n\n- The missing `sales` for \"masonry nail\" says that it was in the lookup table `desc`, but we didn't sell any.\n\n\n## The same thing, but with `anti_join`\n\nAnything in first df but not in second?\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ndesc %>% anti_join(sales)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n# A tibble: 1 x 5\n product_code description size qty price\n \n1 061-5329-4 masonry nail \"1.5\\\"\" 112 8.19\n```\n:::\n:::\n\n\n\nMasonry nails are the only thing in our product description file that we did not sell any of.\n\n", - "supporting": [ - "choosing_files" - ], - "filters": [ - "rmarkdown/pagebreak.lua" - ], - "includes": {}, - "engineDependencies": {}, - "preserve": null, - "postProcess": false - } -} \ No newline at end of file diff --git a/choosing_files/libs/bootstrap/bootstrap-icons.css b/choosing_files/libs/bootstrap/bootstrap-icons.css deleted file mode 100644 index 94f1940..0000000 --- a/choosing_files/libs/bootstrap/bootstrap-icons.css +++ /dev/null @@ -1,2018 +0,0 @@ -@font-face { - font-display: block; - font-family: "bootstrap-icons"; - src: -url("./bootstrap-icons.woff?2ab2cbbe07fcebb53bdaa7313bb290f2") format("woff"); -} - -.bi::before, -[class^="bi-"]::before, -[class*=" bi-"]::before { - display: inline-block; - font-family: bootstrap-icons !important; - font-style: normal; - font-weight: normal !important; - font-variant: normal; - text-transform: none; - line-height: 1; - vertical-align: -.125em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.bi-123::before { content: "\f67f"; } -.bi-alarm-fill::before { content: "\f101"; } -.bi-alarm::before { content: "\f102"; } -.bi-align-bottom::before { content: "\f103"; } -.bi-align-center::before { content: "\f104"; } -.bi-align-end::before { content: "\f105"; } -.bi-align-middle::before { content: "\f106"; } -.bi-align-start::before { content: "\f107"; } -.bi-align-top::before { content: "\f108"; } -.bi-alt::before { content: "\f109"; } -.bi-app-indicator::before { content: "\f10a"; } -.bi-app::before { content: "\f10b"; } -.bi-archive-fill::before { content: "\f10c"; } -.bi-archive::before { content: "\f10d"; } -.bi-arrow-90deg-down::before { content: "\f10e"; } -.bi-arrow-90deg-left::before { content: "\f10f"; } -.bi-arrow-90deg-right::before { content: "\f110"; } -.bi-arrow-90deg-up::before { content: "\f111"; } -.bi-arrow-bar-down::before { content: "\f112"; } -.bi-arrow-bar-left::before { content: "\f113"; } -.bi-arrow-bar-right::before { content: "\f114"; } -.bi-arrow-bar-up::before { content: "\f115"; } -.bi-arrow-clockwise::before { content: "\f116"; } -.bi-arrow-counterclockwise::before { content: "\f117"; } -.bi-arrow-down-circle-fill::before { content: "\f118"; } -.bi-arrow-down-circle::before { content: "\f119"; } -.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } -.bi-arrow-down-left-circle::before { content: "\f11b"; } -.bi-arrow-down-left-square-fill::before { content: "\f11c"; } -.bi-arrow-down-left-square::before { content: "\f11d"; } -.bi-arrow-down-left::before { content: "\f11e"; } -.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } -.bi-arrow-down-right-circle::before { content: "\f120"; } -.bi-arrow-down-right-square-fill::before { content: "\f121"; } -.bi-arrow-down-right-square::before { content: "\f122"; } -.bi-arrow-down-right::before { content: "\f123"; } -.bi-arrow-down-short::before { content: "\f124"; } -.bi-arrow-down-square-fill::before { content: "\f125"; } -.bi-arrow-down-square::before { content: "\f126"; } -.bi-arrow-down-up::before { content: "\f127"; } -.bi-arrow-down::before { content: "\f128"; } -.bi-arrow-left-circle-fill::before { content: "\f129"; } -.bi-arrow-left-circle::before { content: "\f12a"; } -.bi-arrow-left-right::before { content: "\f12b"; } -.bi-arrow-left-short::before { content: "\f12c"; } -.bi-arrow-left-square-fill::before { content: "\f12d"; } -.bi-arrow-left-square::before { content: "\f12e"; } -.bi-arrow-left::before { content: "\f12f"; } -.bi-arrow-repeat::before { content: "\f130"; } -.bi-arrow-return-left::before { content: "\f131"; } -.bi-arrow-return-right::before { content: "\f132"; } -.bi-arrow-right-circle-fill::before { content: "\f133"; } -.bi-arrow-right-circle::before { content: "\f134"; } -.bi-arrow-right-short::before { content: "\f135"; } -.bi-arrow-right-square-fill::before { content: "\f136"; } -.bi-arrow-right-square::before { content: "\f137"; } -.bi-arrow-right::before { content: "\f138"; } -.bi-arrow-up-circle-fill::before { content: "\f139"; } -.bi-arrow-up-circle::before { content: "\f13a"; } -.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } -.bi-arrow-up-left-circle::before { content: "\f13c"; } -.bi-arrow-up-left-square-fill::before { content: "\f13d"; } -.bi-arrow-up-left-square::before { content: "\f13e"; } -.bi-arrow-up-left::before { content: "\f13f"; } -.bi-arrow-up-right-circle-fill::before { content: "\f140"; } -.bi-arrow-up-right-circle::before { content: "\f141"; } -.bi-arrow-up-right-square-fill::before { content: "\f142"; } -.bi-arrow-up-right-square::before { content: "\f143"; } -.bi-arrow-up-right::before { content: "\f144"; } -.bi-arrow-up-short::before { content: "\f145"; } -.bi-arrow-up-square-fill::before { content: "\f146"; } -.bi-arrow-up-square::before { content: "\f147"; } -.bi-arrow-up::before { content: "\f148"; } -.bi-arrows-angle-contract::before { content: "\f149"; } -.bi-arrows-angle-expand::before { content: "\f14a"; } -.bi-arrows-collapse::before { content: "\f14b"; } -.bi-arrows-expand::before { content: "\f14c"; } -.bi-arrows-fullscreen::before { content: "\f14d"; } -.bi-arrows-move::before { content: "\f14e"; } -.bi-aspect-ratio-fill::before { content: "\f14f"; } -.bi-aspect-ratio::before { content: "\f150"; } -.bi-asterisk::before { content: "\f151"; } -.bi-at::before { content: "\f152"; } -.bi-award-fill::before { content: "\f153"; } -.bi-award::before { content: "\f154"; } -.bi-back::before { content: "\f155"; } -.bi-backspace-fill::before { content: "\f156"; } -.bi-backspace-reverse-fill::before { content: "\f157"; } -.bi-backspace-reverse::before { content: "\f158"; } -.bi-backspace::before { content: "\f159"; } -.bi-badge-3d-fill::before { content: "\f15a"; } -.bi-badge-3d::before { content: "\f15b"; } -.bi-badge-4k-fill::before { content: "\f15c"; } -.bi-badge-4k::before { content: "\f15d"; } -.bi-badge-8k-fill::before { content: "\f15e"; } -.bi-badge-8k::before { content: "\f15f"; } -.bi-badge-ad-fill::before { content: "\f160"; } -.bi-badge-ad::before { content: "\f161"; } -.bi-badge-ar-fill::before { content: "\f162"; } -.bi-badge-ar::before { content: "\f163"; } -.bi-badge-cc-fill::before { content: "\f164"; } -.bi-badge-cc::before { content: "\f165"; } -.bi-badge-hd-fill::before { content: "\f166"; } -.bi-badge-hd::before { content: "\f167"; } -.bi-badge-tm-fill::before { content: "\f168"; } -.bi-badge-tm::before { content: "\f169"; } -.bi-badge-vo-fill::before { content: "\f16a"; } -.bi-badge-vo::before { content: "\f16b"; } -.bi-badge-vr-fill::before { content: "\f16c"; } -.bi-badge-vr::before { content: "\f16d"; } -.bi-badge-wc-fill::before { content: "\f16e"; } -.bi-badge-wc::before { content: "\f16f"; } -.bi-bag-check-fill::before { content: "\f170"; } -.bi-bag-check::before { content: "\f171"; } -.bi-bag-dash-fill::before { content: "\f172"; } -.bi-bag-dash::before { content: "\f173"; } -.bi-bag-fill::before { content: "\f174"; } -.bi-bag-plus-fill::before { content: "\f175"; } -.bi-bag-plus::before { content: "\f176"; } -.bi-bag-x-fill::before { content: "\f177"; } -.bi-bag-x::before { content: "\f178"; } -.bi-bag::before { content: "\f179"; } -.bi-bar-chart-fill::before { content: "\f17a"; } -.bi-bar-chart-line-fill::before { content: "\f17b"; } -.bi-bar-chart-line::before { content: "\f17c"; } -.bi-bar-chart-steps::before { content: "\f17d"; } -.bi-bar-chart::before { content: "\f17e"; } -.bi-basket-fill::before { content: "\f17f"; } -.bi-basket::before { content: "\f180"; } -.bi-basket2-fill::before { content: "\f181"; } -.bi-basket2::before { content: "\f182"; } -.bi-basket3-fill::before { content: "\f183"; } -.bi-basket3::before { content: "\f184"; } -.bi-battery-charging::before { content: "\f185"; } -.bi-battery-full::before { content: "\f186"; } -.bi-battery-half::before { content: "\f187"; } -.bi-battery::before { content: "\f188"; } -.bi-bell-fill::before { content: "\f189"; } -.bi-bell::before { content: "\f18a"; } -.bi-bezier::before { content: "\f18b"; } -.bi-bezier2::before { content: "\f18c"; } -.bi-bicycle::before { content: "\f18d"; } -.bi-binoculars-fill::before { content: "\f18e"; } -.bi-binoculars::before { content: "\f18f"; } -.bi-blockquote-left::before { content: "\f190"; } -.bi-blockquote-right::before { content: "\f191"; } -.bi-book-fill::before { content: "\f192"; } -.bi-book-half::before { content: "\f193"; } -.bi-book::before { content: "\f194"; } -.bi-bookmark-check-fill::before { content: "\f195"; } -.bi-bookmark-check::before { content: "\f196"; } -.bi-bookmark-dash-fill::before { content: "\f197"; } -.bi-bookmark-dash::before { content: "\f198"; } -.bi-bookmark-fill::before { content: "\f199"; } -.bi-bookmark-heart-fill::before { content: "\f19a"; } -.bi-bookmark-heart::before { content: "\f19b"; } -.bi-bookmark-plus-fill::before { content: "\f19c"; } -.bi-bookmark-plus::before { content: "\f19d"; } -.bi-bookmark-star-fill::before { content: "\f19e"; } -.bi-bookmark-star::before { content: "\f19f"; } -.bi-bookmark-x-fill::before { content: "\f1a0"; } -.bi-bookmark-x::before { content: "\f1a1"; } -.bi-bookmark::before { content: "\f1a2"; } -.bi-bookmarks-fill::before { content: "\f1a3"; } -.bi-bookmarks::before { content: "\f1a4"; } -.bi-bookshelf::before { content: "\f1a5"; } -.bi-bootstrap-fill::before { content: "\f1a6"; } -.bi-bootstrap-reboot::before { content: "\f1a7"; } -.bi-bootstrap::before { content: "\f1a8"; } -.bi-border-all::before { content: "\f1a9"; } -.bi-border-bottom::before { content: "\f1aa"; } -.bi-border-center::before { content: "\f1ab"; } -.bi-border-inner::before { content: "\f1ac"; } -.bi-border-left::before { content: "\f1ad"; } -.bi-border-middle::before { content: "\f1ae"; } -.bi-border-outer::before { content: "\f1af"; } -.bi-border-right::before { content: "\f1b0"; } -.bi-border-style::before { content: "\f1b1"; } -.bi-border-top::before { content: "\f1b2"; } -.bi-border-width::before { content: "\f1b3"; } -.bi-border::before { content: "\f1b4"; } -.bi-bounding-box-circles::before { content: "\f1b5"; } -.bi-bounding-box::before { content: "\f1b6"; } -.bi-box-arrow-down-left::before { content: "\f1b7"; } -.bi-box-arrow-down-right::before { content: "\f1b8"; } -.bi-box-arrow-down::before { content: "\f1b9"; } -.bi-box-arrow-in-down-left::before { content: "\f1ba"; } -.bi-box-arrow-in-down-right::before { content: "\f1bb"; } -.bi-box-arrow-in-down::before { content: "\f1bc"; } -.bi-box-arrow-in-left::before { content: "\f1bd"; } -.bi-box-arrow-in-right::before { content: "\f1be"; } -.bi-box-arrow-in-up-left::before { content: "\f1bf"; } -.bi-box-arrow-in-up-right::before { content: "\f1c0"; } -.bi-box-arrow-in-up::before { content: "\f1c1"; } -.bi-box-arrow-left::before { content: "\f1c2"; } -.bi-box-arrow-right::before { content: "\f1c3"; } -.bi-box-arrow-up-left::before { content: "\f1c4"; } -.bi-box-arrow-up-right::before { content: "\f1c5"; } -.bi-box-arrow-up::before { content: "\f1c6"; } -.bi-box-seam::before { content: "\f1c7"; } -.bi-box::before { content: "\f1c8"; } -.bi-braces::before { content: "\f1c9"; } -.bi-bricks::before { content: "\f1ca"; } -.bi-briefcase-fill::before { content: "\f1cb"; } -.bi-briefcase::before { content: "\f1cc"; } -.bi-brightness-alt-high-fill::before { content: "\f1cd"; } -.bi-brightness-alt-high::before { content: "\f1ce"; } -.bi-brightness-alt-low-fill::before { content: "\f1cf"; } -.bi-brightness-alt-low::before { content: "\f1d0"; } -.bi-brightness-high-fill::before { content: "\f1d1"; } -.bi-brightness-high::before { content: "\f1d2"; } -.bi-brightness-low-fill::before { content: "\f1d3"; } -.bi-brightness-low::before { content: "\f1d4"; } -.bi-broadcast-pin::before { content: "\f1d5"; } -.bi-broadcast::before { content: "\f1d6"; } -.bi-brush-fill::before { content: "\f1d7"; } -.bi-brush::before { content: "\f1d8"; } -.bi-bucket-fill::before { content: "\f1d9"; } -.bi-bucket::before { content: "\f1da"; } -.bi-bug-fill::before { content: "\f1db"; } -.bi-bug::before { content: "\f1dc"; } -.bi-building::before { content: "\f1dd"; } -.bi-bullseye::before { content: "\f1de"; } -.bi-calculator-fill::before { content: "\f1df"; } -.bi-calculator::before { content: "\f1e0"; } -.bi-calendar-check-fill::before { content: "\f1e1"; } -.bi-calendar-check::before { content: "\f1e2"; } -.bi-calendar-date-fill::before { content: "\f1e3"; } -.bi-calendar-date::before { content: "\f1e4"; } -.bi-calendar-day-fill::before { content: "\f1e5"; } -.bi-calendar-day::before { content: "\f1e6"; } -.bi-calendar-event-fill::before { content: "\f1e7"; } -.bi-calendar-event::before { content: "\f1e8"; } -.bi-calendar-fill::before { content: "\f1e9"; } -.bi-calendar-minus-fill::before { content: "\f1ea"; } -.bi-calendar-minus::before { content: "\f1eb"; } -.bi-calendar-month-fill::before { content: "\f1ec"; } -.bi-calendar-month::before { content: "\f1ed"; } -.bi-calendar-plus-fill::before { content: "\f1ee"; } -.bi-calendar-plus::before { content: "\f1ef"; } -.bi-calendar-range-fill::before { content: "\f1f0"; } -.bi-calendar-range::before { content: "\f1f1"; } -.bi-calendar-week-fill::before { content: "\f1f2"; } -.bi-calendar-week::before { content: "\f1f3"; } -.bi-calendar-x-fill::before { content: "\f1f4"; } -.bi-calendar-x::before { content: "\f1f5"; } -.bi-calendar::before { content: "\f1f6"; } -.bi-calendar2-check-fill::before { content: "\f1f7"; } -.bi-calendar2-check::before { content: "\f1f8"; } -.bi-calendar2-date-fill::before { content: "\f1f9"; } -.bi-calendar2-date::before { content: "\f1fa"; } -.bi-calendar2-day-fill::before { content: "\f1fb"; } -.bi-calendar2-day::before { content: "\f1fc"; } -.bi-calendar2-event-fill::before { content: "\f1fd"; } -.bi-calendar2-event::before { content: "\f1fe"; } -.bi-calendar2-fill::before { content: "\f1ff"; } -.bi-calendar2-minus-fill::before { content: "\f200"; } -.bi-calendar2-minus::before { content: "\f201"; } -.bi-calendar2-month-fill::before { content: "\f202"; } -.bi-calendar2-month::before { content: "\f203"; } -.bi-calendar2-plus-fill::before { content: "\f204"; } -.bi-calendar2-plus::before { content: "\f205"; } -.bi-calendar2-range-fill::before { content: "\f206"; } -.bi-calendar2-range::before { content: "\f207"; } -.bi-calendar2-week-fill::before { content: "\f208"; } -.bi-calendar2-week::before { content: "\f209"; } -.bi-calendar2-x-fill::before { content: "\f20a"; } -.bi-calendar2-x::before { content: "\f20b"; } -.bi-calendar2::before { content: "\f20c"; } -.bi-calendar3-event-fill::before { content: "\f20d"; } -.bi-calendar3-event::before { content: "\f20e"; } -.bi-calendar3-fill::before { content: "\f20f"; } -.bi-calendar3-range-fill::before { content: "\f210"; } -.bi-calendar3-range::before { content: "\f211"; } -.bi-calendar3-week-fill::before { content: "\f212"; } -.bi-calendar3-week::before { content: "\f213"; } -.bi-calendar3::before { content: "\f214"; } -.bi-calendar4-event::before { content: "\f215"; } -.bi-calendar4-range::before { content: "\f216"; } -.bi-calendar4-week::before { content: "\f217"; } -.bi-calendar4::before { content: "\f218"; } -.bi-camera-fill::before { content: "\f219"; } -.bi-camera-reels-fill::before { content: "\f21a"; } -.bi-camera-reels::before { content: "\f21b"; } -.bi-camera-video-fill::before { content: "\f21c"; } -.bi-camera-video-off-fill::before { content: "\f21d"; } -.bi-camera-video-off::before { content: "\f21e"; } -.bi-camera-video::before { content: "\f21f"; } -.bi-camera::before { content: "\f220"; } -.bi-camera2::before { content: "\f221"; } -.bi-capslock-fill::before { content: "\f222"; } -.bi-capslock::before { content: "\f223"; } -.bi-card-checklist::before { content: "\f224"; } -.bi-card-heading::before { content: "\f225"; } -.bi-card-image::before { content: "\f226"; } -.bi-card-list::before { content: "\f227"; } -.bi-card-text::before { content: "\f228"; } -.bi-caret-down-fill::before { content: "\f229"; } -.bi-caret-down-square-fill::before { content: "\f22a"; } -.bi-caret-down-square::before { content: "\f22b"; } -.bi-caret-down::before { content: "\f22c"; } -.bi-caret-left-fill::before { content: "\f22d"; } -.bi-caret-left-square-fill::before { content: "\f22e"; } -.bi-caret-left-square::before { content: "\f22f"; } -.bi-caret-left::before { content: "\f230"; } -.bi-caret-right-fill::before { content: "\f231"; } -.bi-caret-right-square-fill::before { content: "\f232"; } -.bi-caret-right-square::before { content: "\f233"; } -.bi-caret-right::before { content: "\f234"; } -.bi-caret-up-fill::before { content: "\f235"; } -.bi-caret-up-square-fill::before { content: "\f236"; } -.bi-caret-up-square::before { content: "\f237"; } -.bi-caret-up::before { content: "\f238"; } -.bi-cart-check-fill::before { content: "\f239"; } -.bi-cart-check::before { content: "\f23a"; } -.bi-cart-dash-fill::before { content: "\f23b"; } -.bi-cart-dash::before { content: "\f23c"; } -.bi-cart-fill::before { content: "\f23d"; } -.bi-cart-plus-fill::before { content: "\f23e"; } -.bi-cart-plus::before { content: "\f23f"; } -.bi-cart-x-fill::before { content: "\f240"; } -.bi-cart-x::before { content: "\f241"; } -.bi-cart::before { content: "\f242"; } -.bi-cart2::before { content: "\f243"; } -.bi-cart3::before { content: "\f244"; } -.bi-cart4::before { content: "\f245"; } -.bi-cash-stack::before { content: "\f246"; } -.bi-cash::before { content: "\f247"; } -.bi-cast::before { content: "\f248"; } -.bi-chat-dots-fill::before { content: "\f249"; } -.bi-chat-dots::before { content: "\f24a"; } -.bi-chat-fill::before { content: "\f24b"; } -.bi-chat-left-dots-fill::before { content: "\f24c"; } -.bi-chat-left-dots::before { content: "\f24d"; } -.bi-chat-left-fill::before { content: "\f24e"; } -.bi-chat-left-quote-fill::before { content: "\f24f"; } -.bi-chat-left-quote::before { content: "\f250"; } -.bi-chat-left-text-fill::before { content: "\f251"; } -.bi-chat-left-text::before { content: "\f252"; } -.bi-chat-left::before { content: "\f253"; } -.bi-chat-quote-fill::before { content: "\f254"; } -.bi-chat-quote::before { content: "\f255"; } -.bi-chat-right-dots-fill::before { content: "\f256"; } -.bi-chat-right-dots::before { content: "\f257"; } -.bi-chat-right-fill::before { content: "\f258"; } -.bi-chat-right-quote-fill::before { content: "\f259"; } -.bi-chat-right-quote::before { content: "\f25a"; } -.bi-chat-right-text-fill::before { content: "\f25b"; } -.bi-chat-right-text::before { content: "\f25c"; } -.bi-chat-right::before { content: "\f25d"; } -.bi-chat-square-dots-fill::before { content: "\f25e"; } -.bi-chat-square-dots::before { content: "\f25f"; } -.bi-chat-square-fill::before { content: "\f260"; } -.bi-chat-square-quote-fill::before { content: "\f261"; } -.bi-chat-square-quote::before { content: "\f262"; } -.bi-chat-square-text-fill::before { content: "\f263"; } -.bi-chat-square-text::before { content: "\f264"; } -.bi-chat-square::before { content: "\f265"; } -.bi-chat-text-fill::before { content: "\f266"; } -.bi-chat-text::before { content: "\f267"; } -.bi-chat::before { content: "\f268"; } -.bi-check-all::before { content: "\f269"; } -.bi-check-circle-fill::before { content: "\f26a"; } -.bi-check-circle::before { content: "\f26b"; } -.bi-check-square-fill::before { content: "\f26c"; } -.bi-check-square::before { content: "\f26d"; } -.bi-check::before { content: "\f26e"; } -.bi-check2-all::before { content: "\f26f"; } -.bi-check2-circle::before { content: "\f270"; } -.bi-check2-square::before { content: "\f271"; } -.bi-check2::before { content: "\f272"; } -.bi-chevron-bar-contract::before { content: "\f273"; } -.bi-chevron-bar-down::before { content: "\f274"; } -.bi-chevron-bar-expand::before { content: "\f275"; } -.bi-chevron-bar-left::before { content: "\f276"; } -.bi-chevron-bar-right::before { content: "\f277"; } -.bi-chevron-bar-up::before { content: "\f278"; } -.bi-chevron-compact-down::before { content: "\f279"; } -.bi-chevron-compact-left::before { content: "\f27a"; } -.bi-chevron-compact-right::before { content: "\f27b"; } -.bi-chevron-compact-up::before { content: "\f27c"; } -.bi-chevron-contract::before { content: "\f27d"; } -.bi-chevron-double-down::before { content: "\f27e"; } -.bi-chevron-double-left::before { content: "\f27f"; } -.bi-chevron-double-right::before { content: "\f280"; } -.bi-chevron-double-up::before { content: "\f281"; } -.bi-chevron-down::before { content: "\f282"; } -.bi-chevron-expand::before { content: "\f283"; } -.bi-chevron-left::before { content: "\f284"; } -.bi-chevron-right::before { content: "\f285"; } -.bi-chevron-up::before { content: "\f286"; } -.bi-circle-fill::before { content: "\f287"; } -.bi-circle-half::before { content: "\f288"; } -.bi-circle-square::before { content: "\f289"; } -.bi-circle::before { content: "\f28a"; } -.bi-clipboard-check::before { content: "\f28b"; } -.bi-clipboard-data::before { content: "\f28c"; } -.bi-clipboard-minus::before { content: "\f28d"; } -.bi-clipboard-plus::before { content: "\f28e"; } -.bi-clipboard-x::before { content: "\f28f"; } -.bi-clipboard::before { content: "\f290"; } -.bi-clock-fill::before { content: "\f291"; } -.bi-clock-history::before { content: "\f292"; } -.bi-clock::before { content: "\f293"; } -.bi-cloud-arrow-down-fill::before { content: "\f294"; } -.bi-cloud-arrow-down::before { content: "\f295"; } -.bi-cloud-arrow-up-fill::before { content: "\f296"; } -.bi-cloud-arrow-up::before { content: "\f297"; } -.bi-cloud-check-fill::before { content: "\f298"; } -.bi-cloud-check::before { content: "\f299"; } -.bi-cloud-download-fill::before { content: "\f29a"; } -.bi-cloud-download::before { content: "\f29b"; } -.bi-cloud-drizzle-fill::before { content: "\f29c"; } -.bi-cloud-drizzle::before { content: "\f29d"; } -.bi-cloud-fill::before { content: "\f29e"; } -.bi-cloud-fog-fill::before { content: "\f29f"; } -.bi-cloud-fog::before { content: "\f2a0"; } -.bi-cloud-fog2-fill::before { content: "\f2a1"; } -.bi-cloud-fog2::before { content: "\f2a2"; } -.bi-cloud-hail-fill::before { content: "\f2a3"; } -.bi-cloud-hail::before { content: "\f2a4"; } -.bi-cloud-haze-1::before { content: "\f2a5"; } -.bi-cloud-haze-fill::before { content: "\f2a6"; } -.bi-cloud-haze::before { content: "\f2a7"; } -.bi-cloud-haze2-fill::before { content: "\f2a8"; } -.bi-cloud-lightning-fill::before { content: "\f2a9"; } -.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } -.bi-cloud-lightning-rain::before { content: "\f2ab"; } -.bi-cloud-lightning::before { content: "\f2ac"; } -.bi-cloud-minus-fill::before { content: "\f2ad"; } -.bi-cloud-minus::before { content: "\f2ae"; } -.bi-cloud-moon-fill::before { content: "\f2af"; } -.bi-cloud-moon::before { content: "\f2b0"; } -.bi-cloud-plus-fill::before { content: "\f2b1"; } -.bi-cloud-plus::before { content: "\f2b2"; } -.bi-cloud-rain-fill::before { content: "\f2b3"; } -.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } -.bi-cloud-rain-heavy::before { content: "\f2b5"; } -.bi-cloud-rain::before { content: "\f2b6"; } -.bi-cloud-slash-fill::before { content: "\f2b7"; } -.bi-cloud-slash::before { content: "\f2b8"; } -.bi-cloud-sleet-fill::before { content: "\f2b9"; } -.bi-cloud-sleet::before { content: "\f2ba"; } -.bi-cloud-snow-fill::before { content: "\f2bb"; } -.bi-cloud-snow::before { content: "\f2bc"; } -.bi-cloud-sun-fill::before { content: "\f2bd"; } -.bi-cloud-sun::before { content: "\f2be"; } -.bi-cloud-upload-fill::before { content: "\f2bf"; } -.bi-cloud-upload::before { content: "\f2c0"; } -.bi-cloud::before { content: "\f2c1"; } -.bi-clouds-fill::before { content: "\f2c2"; } -.bi-clouds::before { content: "\f2c3"; } -.bi-cloudy-fill::before { content: "\f2c4"; } -.bi-cloudy::before { content: "\f2c5"; } -.bi-code-slash::before { content: "\f2c6"; } -.bi-code-square::before { content: "\f2c7"; } -.bi-code::before { content: "\f2c8"; } -.bi-collection-fill::before { content: "\f2c9"; } -.bi-collection-play-fill::before { content: "\f2ca"; } -.bi-collection-play::before { content: "\f2cb"; } -.bi-collection::before { content: "\f2cc"; } -.bi-columns-gap::before { content: "\f2cd"; } -.bi-columns::before { content: "\f2ce"; } -.bi-command::before { content: "\f2cf"; } -.bi-compass-fill::before { content: "\f2d0"; } -.bi-compass::before { content: "\f2d1"; } -.bi-cone-striped::before { content: "\f2d2"; } -.bi-cone::before { content: "\f2d3"; } -.bi-controller::before { content: "\f2d4"; } -.bi-cpu-fill::before { content: "\f2d5"; } -.bi-cpu::before { content: "\f2d6"; } -.bi-credit-card-2-back-fill::before { content: "\f2d7"; } -.bi-credit-card-2-back::before { content: "\f2d8"; } -.bi-credit-card-2-front-fill::before { content: "\f2d9"; } -.bi-credit-card-2-front::before { content: "\f2da"; } -.bi-credit-card-fill::before { content: "\f2db"; } -.bi-credit-card::before { content: "\f2dc"; } -.bi-crop::before { content: "\f2dd"; } -.bi-cup-fill::before { content: "\f2de"; } -.bi-cup-straw::before { content: "\f2df"; } -.bi-cup::before { content: "\f2e0"; } -.bi-cursor-fill::before { content: "\f2e1"; } -.bi-cursor-text::before { content: "\f2e2"; } -.bi-cursor::before { content: "\f2e3"; } -.bi-dash-circle-dotted::before { content: "\f2e4"; } -.bi-dash-circle-fill::before { content: "\f2e5"; } -.bi-dash-circle::before { content: "\f2e6"; } -.bi-dash-square-dotted::before { content: "\f2e7"; } -.bi-dash-square-fill::before { content: "\f2e8"; } -.bi-dash-square::before { content: "\f2e9"; } -.bi-dash::before { content: "\f2ea"; } -.bi-diagram-2-fill::before { content: "\f2eb"; } -.bi-diagram-2::before { content: "\f2ec"; } -.bi-diagram-3-fill::before { content: "\f2ed"; } -.bi-diagram-3::before { content: "\f2ee"; } -.bi-diamond-fill::before { content: "\f2ef"; } -.bi-diamond-half::before { content: "\f2f0"; } -.bi-diamond::before { content: "\f2f1"; } -.bi-dice-1-fill::before { content: "\f2f2"; } -.bi-dice-1::before { content: "\f2f3"; } -.bi-dice-2-fill::before { content: "\f2f4"; } -.bi-dice-2::before { content: "\f2f5"; } -.bi-dice-3-fill::before { content: "\f2f6"; } -.bi-dice-3::before { content: "\f2f7"; } -.bi-dice-4-fill::before { content: "\f2f8"; } -.bi-dice-4::before { content: "\f2f9"; } -.bi-dice-5-fill::before { content: "\f2fa"; } -.bi-dice-5::before { content: "\f2fb"; } -.bi-dice-6-fill::before { content: "\f2fc"; } -.bi-dice-6::before { content: "\f2fd"; } -.bi-disc-fill::before { content: "\f2fe"; } -.bi-disc::before { content: "\f2ff"; } -.bi-discord::before { content: "\f300"; } -.bi-display-fill::before { content: "\f301"; } -.bi-display::before { content: "\f302"; } -.bi-distribute-horizontal::before { content: "\f303"; } -.bi-distribute-vertical::before { content: "\f304"; } -.bi-door-closed-fill::before { content: "\f305"; } -.bi-door-closed::before { content: "\f306"; } -.bi-door-open-fill::before { content: "\f307"; } -.bi-door-open::before { content: "\f308"; } -.bi-dot::before { content: "\f309"; } -.bi-download::before { content: "\f30a"; } -.bi-droplet-fill::before { content: "\f30b"; } -.bi-droplet-half::before { content: "\f30c"; } -.bi-droplet::before { content: "\f30d"; } -.bi-earbuds::before { content: "\f30e"; } -.bi-easel-fill::before { content: "\f30f"; } -.bi-easel::before { content: "\f310"; } -.bi-egg-fill::before { content: "\f311"; } -.bi-egg-fried::before { content: "\f312"; } -.bi-egg::before { content: "\f313"; } -.bi-eject-fill::before { content: "\f314"; } -.bi-eject::before { content: "\f315"; } -.bi-emoji-angry-fill::before { content: "\f316"; } -.bi-emoji-angry::before { content: "\f317"; } -.bi-emoji-dizzy-fill::before { content: "\f318"; } -.bi-emoji-dizzy::before { content: "\f319"; } -.bi-emoji-expressionless-fill::before { content: "\f31a"; } -.bi-emoji-expressionless::before { content: "\f31b"; } -.bi-emoji-frown-fill::before { content: "\f31c"; } -.bi-emoji-frown::before { content: "\f31d"; } -.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } -.bi-emoji-heart-eyes::before { content: "\f31f"; } -.bi-emoji-laughing-fill::before { content: "\f320"; } -.bi-emoji-laughing::before { content: "\f321"; } -.bi-emoji-neutral-fill::before { content: "\f322"; } -.bi-emoji-neutral::before { content: "\f323"; } -.bi-emoji-smile-fill::before { content: "\f324"; } -.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } -.bi-emoji-smile-upside-down::before { content: "\f326"; } -.bi-emoji-smile::before { content: "\f327"; } -.bi-emoji-sunglasses-fill::before { content: "\f328"; } -.bi-emoji-sunglasses::before { content: "\f329"; } -.bi-emoji-wink-fill::before { content: "\f32a"; } -.bi-emoji-wink::before { content: "\f32b"; } -.bi-envelope-fill::before { content: "\f32c"; } -.bi-envelope-open-fill::before { content: "\f32d"; } -.bi-envelope-open::before { content: "\f32e"; } -.bi-envelope::before { content: "\f32f"; } -.bi-eraser-fill::before { content: "\f330"; } -.bi-eraser::before { content: "\f331"; } -.bi-exclamation-circle-fill::before { content: "\f332"; } -.bi-exclamation-circle::before { content: "\f333"; } -.bi-exclamation-diamond-fill::before { content: "\f334"; } -.bi-exclamation-diamond::before { content: "\f335"; } -.bi-exclamation-octagon-fill::before { content: "\f336"; } -.bi-exclamation-octagon::before { content: "\f337"; } -.bi-exclamation-square-fill::before { content: "\f338"; } -.bi-exclamation-square::before { content: "\f339"; } -.bi-exclamation-triangle-fill::before { content: "\f33a"; } -.bi-exclamation-triangle::before { content: "\f33b"; } -.bi-exclamation::before { content: "\f33c"; } -.bi-exclude::before { content: "\f33d"; } -.bi-eye-fill::before { content: "\f33e"; } -.bi-eye-slash-fill::before { content: "\f33f"; } -.bi-eye-slash::before { content: "\f340"; } -.bi-eye::before { content: "\f341"; } -.bi-eyedropper::before { content: "\f342"; } -.bi-eyeglasses::before { content: "\f343"; } -.bi-facebook::before { content: "\f344"; } -.bi-file-arrow-down-fill::before { content: "\f345"; } -.bi-file-arrow-down::before { content: "\f346"; } -.bi-file-arrow-up-fill::before { content: "\f347"; } -.bi-file-arrow-up::before { content: "\f348"; } -.bi-file-bar-graph-fill::before { content: "\f349"; } -.bi-file-bar-graph::before { content: "\f34a"; } -.bi-file-binary-fill::before { content: "\f34b"; } -.bi-file-binary::before { content: "\f34c"; } -.bi-file-break-fill::before { content: "\f34d"; } -.bi-file-break::before { content: "\f34e"; } -.bi-file-check-fill::before { content: "\f34f"; } -.bi-file-check::before { content: "\f350"; } -.bi-file-code-fill::before { content: "\f351"; } -.bi-file-code::before { content: "\f352"; } -.bi-file-diff-fill::before { content: "\f353"; } -.bi-file-diff::before { content: "\f354"; } -.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } -.bi-file-earmark-arrow-down::before { content: "\f356"; } -.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } -.bi-file-earmark-arrow-up::before { content: "\f358"; } -.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } -.bi-file-earmark-bar-graph::before { content: "\f35a"; } -.bi-file-earmark-binary-fill::before { content: "\f35b"; } -.bi-file-earmark-binary::before { content: "\f35c"; } -.bi-file-earmark-break-fill::before { content: "\f35d"; } -.bi-file-earmark-break::before { content: "\f35e"; } -.bi-file-earmark-check-fill::before { content: "\f35f"; } -.bi-file-earmark-check::before { content: "\f360"; } -.bi-file-earmark-code-fill::before { content: "\f361"; } -.bi-file-earmark-code::before { content: "\f362"; } -.bi-file-earmark-diff-fill::before { content: "\f363"; } -.bi-file-earmark-diff::before { content: "\f364"; } -.bi-file-earmark-easel-fill::before { content: "\f365"; } -.bi-file-earmark-easel::before { content: "\f366"; } -.bi-file-earmark-excel-fill::before { content: "\f367"; } -.bi-file-earmark-excel::before { content: "\f368"; } -.bi-file-earmark-fill::before { content: "\f369"; } -.bi-file-earmark-font-fill::before { content: "\f36a"; } -.bi-file-earmark-font::before { content: "\f36b"; } -.bi-file-earmark-image-fill::before { content: "\f36c"; } -.bi-file-earmark-image::before { content: "\f36d"; } -.bi-file-earmark-lock-fill::before { content: "\f36e"; } -.bi-file-earmark-lock::before { content: "\f36f"; } -.bi-file-earmark-lock2-fill::before { content: "\f370"; } -.bi-file-earmark-lock2::before { content: "\f371"; } -.bi-file-earmark-medical-fill::before { content: "\f372"; } -.bi-file-earmark-medical::before { content: "\f373"; } -.bi-file-earmark-minus-fill::before { content: "\f374"; } -.bi-file-earmark-minus::before { content: "\f375"; } -.bi-file-earmark-music-fill::before { content: "\f376"; } -.bi-file-earmark-music::before { content: "\f377"; } -.bi-file-earmark-person-fill::before { content: "\f378"; } -.bi-file-earmark-person::before { content: "\f379"; } -.bi-file-earmark-play-fill::before { content: "\f37a"; } -.bi-file-earmark-play::before { content: "\f37b"; } -.bi-file-earmark-plus-fill::before { content: "\f37c"; } -.bi-file-earmark-plus::before { content: "\f37d"; } -.bi-file-earmark-post-fill::before { content: "\f37e"; } -.bi-file-earmark-post::before { content: "\f37f"; } -.bi-file-earmark-ppt-fill::before { content: "\f380"; } -.bi-file-earmark-ppt::before { content: "\f381"; } -.bi-file-earmark-richtext-fill::before { content: "\f382"; } -.bi-file-earmark-richtext::before { content: "\f383"; } -.bi-file-earmark-ruled-fill::before { content: "\f384"; } -.bi-file-earmark-ruled::before { content: "\f385"; } -.bi-file-earmark-slides-fill::before { content: "\f386"; } -.bi-file-earmark-slides::before { content: "\f387"; } -.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } -.bi-file-earmark-spreadsheet::before { content: "\f389"; } -.bi-file-earmark-text-fill::before { content: "\f38a"; } -.bi-file-earmark-text::before { content: "\f38b"; } -.bi-file-earmark-word-fill::before { content: "\f38c"; } -.bi-file-earmark-word::before { content: "\f38d"; } -.bi-file-earmark-x-fill::before { content: "\f38e"; } -.bi-file-earmark-x::before { content: "\f38f"; } -.bi-file-earmark-zip-fill::before { content: "\f390"; } -.bi-file-earmark-zip::before { content: "\f391"; } -.bi-file-earmark::before { content: "\f392"; } -.bi-file-easel-fill::before { content: "\f393"; } -.bi-file-easel::before { content: "\f394"; } -.bi-file-excel-fill::before { content: "\f395"; } -.bi-file-excel::before { content: "\f396"; } -.bi-file-fill::before { content: "\f397"; } -.bi-file-font-fill::before { content: "\f398"; } -.bi-file-font::before { content: "\f399"; } -.bi-file-image-fill::before { content: "\f39a"; } -.bi-file-image::before { content: "\f39b"; } -.bi-file-lock-fill::before { content: "\f39c"; } -.bi-file-lock::before { content: "\f39d"; } -.bi-file-lock2-fill::before { content: "\f39e"; } -.bi-file-lock2::before { content: "\f39f"; } -.bi-file-medical-fill::before { content: "\f3a0"; } -.bi-file-medical::before { content: "\f3a1"; } -.bi-file-minus-fill::before { content: "\f3a2"; } -.bi-file-minus::before { content: "\f3a3"; } -.bi-file-music-fill::before { content: "\f3a4"; } -.bi-file-music::before { content: "\f3a5"; } -.bi-file-person-fill::before { content: "\f3a6"; } -.bi-file-person::before { content: "\f3a7"; } -.bi-file-play-fill::before { content: "\f3a8"; } -.bi-file-play::before { content: "\f3a9"; } -.bi-file-plus-fill::before { content: "\f3aa"; } -.bi-file-plus::before { content: "\f3ab"; } -.bi-file-post-fill::before { content: "\f3ac"; } -.bi-file-post::before { content: "\f3ad"; } -.bi-file-ppt-fill::before { content: "\f3ae"; } -.bi-file-ppt::before { content: "\f3af"; } -.bi-file-richtext-fill::before { content: "\f3b0"; } -.bi-file-richtext::before { content: "\f3b1"; } -.bi-file-ruled-fill::before { content: "\f3b2"; } -.bi-file-ruled::before { content: "\f3b3"; } -.bi-file-slides-fill::before { content: "\f3b4"; } -.bi-file-slides::before { content: "\f3b5"; } -.bi-file-spreadsheet-fill::before { content: "\f3b6"; } -.bi-file-spreadsheet::before { content: "\f3b7"; } -.bi-file-text-fill::before { content: "\f3b8"; } -.bi-file-text::before { content: "\f3b9"; } -.bi-file-word-fill::before { content: "\f3ba"; } -.bi-file-word::before { content: "\f3bb"; } -.bi-file-x-fill::before { content: "\f3bc"; } -.bi-file-x::before { content: "\f3bd"; } -.bi-file-zip-fill::before { content: "\f3be"; } -.bi-file-zip::before { content: "\f3bf"; } -.bi-file::before { content: "\f3c0"; } -.bi-files-alt::before { content: "\f3c1"; } -.bi-files::before { content: "\f3c2"; } -.bi-film::before { content: "\f3c3"; } -.bi-filter-circle-fill::before { content: "\f3c4"; } -.bi-filter-circle::before { content: "\f3c5"; } -.bi-filter-left::before { content: "\f3c6"; } -.bi-filter-right::before { content: "\f3c7"; } -.bi-filter-square-fill::before { content: "\f3c8"; } -.bi-filter-square::before { content: "\f3c9"; } -.bi-filter::before { content: "\f3ca"; } -.bi-flag-fill::before { content: "\f3cb"; } -.bi-flag::before { content: "\f3cc"; } -.bi-flower1::before { content: "\f3cd"; } -.bi-flower2::before { content: "\f3ce"; } -.bi-flower3::before { content: "\f3cf"; } -.bi-folder-check::before { content: "\f3d0"; } -.bi-folder-fill::before { content: "\f3d1"; } -.bi-folder-minus::before { content: "\f3d2"; } -.bi-folder-plus::before { content: "\f3d3"; } -.bi-folder-symlink-fill::before { content: "\f3d4"; } -.bi-folder-symlink::before { content: "\f3d5"; } -.bi-folder-x::before { content: "\f3d6"; } -.bi-folder::before { content: "\f3d7"; } -.bi-folder2-open::before { content: "\f3d8"; } -.bi-folder2::before { content: "\f3d9"; } -.bi-fonts::before { content: "\f3da"; } -.bi-forward-fill::before { content: "\f3db"; } -.bi-forward::before { content: "\f3dc"; } -.bi-front::before { content: "\f3dd"; } -.bi-fullscreen-exit::before { content: "\f3de"; } -.bi-fullscreen::before { content: "\f3df"; } -.bi-funnel-fill::before { content: "\f3e0"; } -.bi-funnel::before { content: "\f3e1"; } -.bi-gear-fill::before { content: "\f3e2"; } -.bi-gear-wide-connected::before { content: "\f3e3"; } -.bi-gear-wide::before { content: "\f3e4"; } -.bi-gear::before { content: "\f3e5"; } -.bi-gem::before { content: "\f3e6"; } -.bi-geo-alt-fill::before { content: "\f3e7"; } -.bi-geo-alt::before { content: "\f3e8"; } -.bi-geo-fill::before { content: "\f3e9"; } -.bi-geo::before { content: "\f3ea"; } -.bi-gift-fill::before { content: "\f3eb"; } -.bi-gift::before { content: "\f3ec"; } -.bi-github::before { content: "\f3ed"; } -.bi-globe::before { content: "\f3ee"; } -.bi-globe2::before { content: "\f3ef"; } -.bi-google::before { content: "\f3f0"; } -.bi-graph-down::before { content: "\f3f1"; } -.bi-graph-up::before { content: "\f3f2"; } -.bi-grid-1x2-fill::before { content: "\f3f3"; } -.bi-grid-1x2::before { content: "\f3f4"; } -.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } -.bi-grid-3x2-gap::before { content: "\f3f6"; } -.bi-grid-3x2::before { content: "\f3f7"; } -.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } -.bi-grid-3x3-gap::before { content: "\f3f9"; } -.bi-grid-3x3::before { content: "\f3fa"; } -.bi-grid-fill::before { content: "\f3fb"; } -.bi-grid::before { content: "\f3fc"; } -.bi-grip-horizontal::before { content: "\f3fd"; } -.bi-grip-vertical::before { content: "\f3fe"; } -.bi-hammer::before { content: "\f3ff"; } -.bi-hand-index-fill::before { content: "\f400"; } -.bi-hand-index-thumb-fill::before { content: "\f401"; } -.bi-hand-index-thumb::before { content: "\f402"; } -.bi-hand-index::before { content: "\f403"; } -.bi-hand-thumbs-down-fill::before { content: "\f404"; } -.bi-hand-thumbs-down::before { content: "\f405"; } -.bi-hand-thumbs-up-fill::before { content: "\f406"; } -.bi-hand-thumbs-up::before { content: "\f407"; } -.bi-handbag-fill::before { content: "\f408"; } -.bi-handbag::before { content: "\f409"; } -.bi-hash::before { content: "\f40a"; } -.bi-hdd-fill::before { content: "\f40b"; } -.bi-hdd-network-fill::before { content: "\f40c"; } -.bi-hdd-network::before { content: "\f40d"; } -.bi-hdd-rack-fill::before { content: "\f40e"; } -.bi-hdd-rack::before { content: "\f40f"; } -.bi-hdd-stack-fill::before { content: "\f410"; } -.bi-hdd-stack::before { content: "\f411"; } -.bi-hdd::before { content: "\f412"; } -.bi-headphones::before { content: "\f413"; } -.bi-headset::before { content: "\f414"; } -.bi-heart-fill::before { content: "\f415"; } -.bi-heart-half::before { content: "\f416"; } -.bi-heart::before { content: "\f417"; } -.bi-heptagon-fill::before { content: "\f418"; } -.bi-heptagon-half::before { content: "\f419"; } -.bi-heptagon::before { content: "\f41a"; } -.bi-hexagon-fill::before { content: "\f41b"; } -.bi-hexagon-half::before { content: "\f41c"; } -.bi-hexagon::before { content: "\f41d"; } -.bi-hourglass-bottom::before { content: "\f41e"; } -.bi-hourglass-split::before { content: "\f41f"; } -.bi-hourglass-top::before { content: "\f420"; } -.bi-hourglass::before { content: "\f421"; } -.bi-house-door-fill::before { content: "\f422"; } -.bi-house-door::before { content: "\f423"; } -.bi-house-fill::before { content: "\f424"; } -.bi-house::before { content: "\f425"; } -.bi-hr::before { content: "\f426"; } -.bi-hurricane::before { content: "\f427"; } -.bi-image-alt::before { content: "\f428"; } -.bi-image-fill::before { content: "\f429"; } -.bi-image::before { content: "\f42a"; } -.bi-images::before { content: "\f42b"; } -.bi-inbox-fill::before { content: "\f42c"; } -.bi-inbox::before { content: "\f42d"; } -.bi-inboxes-fill::before { content: "\f42e"; } -.bi-inboxes::before { content: "\f42f"; } -.bi-info-circle-fill::before { content: "\f430"; } -.bi-info-circle::before { content: "\f431"; } -.bi-info-square-fill::before { content: "\f432"; } -.bi-info-square::before { content: "\f433"; } -.bi-info::before { content: "\f434"; } -.bi-input-cursor-text::before { content: "\f435"; } -.bi-input-cursor::before { content: "\f436"; } -.bi-instagram::before { content: "\f437"; } -.bi-intersect::before { content: "\f438"; } -.bi-journal-album::before { content: "\f439"; } -.bi-journal-arrow-down::before { content: "\f43a"; } -.bi-journal-arrow-up::before { content: "\f43b"; } -.bi-journal-bookmark-fill::before { content: "\f43c"; } -.bi-journal-bookmark::before { content: "\f43d"; } -.bi-journal-check::before { content: "\f43e"; } -.bi-journal-code::before { content: "\f43f"; } -.bi-journal-medical::before { content: "\f440"; } -.bi-journal-minus::before { content: "\f441"; } -.bi-journal-plus::before { content: "\f442"; } -.bi-journal-richtext::before { content: "\f443"; } -.bi-journal-text::before { content: "\f444"; } -.bi-journal-x::before { content: "\f445"; } -.bi-journal::before { content: "\f446"; } -.bi-journals::before { content: "\f447"; } -.bi-joystick::before { content: "\f448"; } -.bi-justify-left::before { content: "\f449"; } -.bi-justify-right::before { content: "\f44a"; } -.bi-justify::before { content: "\f44b"; } -.bi-kanban-fill::before { content: "\f44c"; } -.bi-kanban::before { content: "\f44d"; } -.bi-key-fill::before { content: "\f44e"; } -.bi-key::before { content: "\f44f"; } -.bi-keyboard-fill::before { content: "\f450"; } -.bi-keyboard::before { content: "\f451"; } -.bi-ladder::before { content: "\f452"; } -.bi-lamp-fill::before { content: "\f453"; } -.bi-lamp::before { content: "\f454"; } -.bi-laptop-fill::before { content: "\f455"; } -.bi-laptop::before { content: "\f456"; } -.bi-layer-backward::before { content: "\f457"; } -.bi-layer-forward::before { content: "\f458"; } -.bi-layers-fill::before { content: "\f459"; } -.bi-layers-half::before { content: "\f45a"; } -.bi-layers::before { content: "\f45b"; } -.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } -.bi-layout-sidebar-inset::before { content: "\f45d"; } -.bi-layout-sidebar-reverse::before { content: "\f45e"; } -.bi-layout-sidebar::before { content: "\f45f"; } -.bi-layout-split::before { content: "\f460"; } -.bi-layout-text-sidebar-reverse::before { content: "\f461"; } -.bi-layout-text-sidebar::before { content: "\f462"; } -.bi-layout-text-window-reverse::before { content: "\f463"; } -.bi-layout-text-window::before { content: "\f464"; } -.bi-layout-three-columns::before { content: "\f465"; } -.bi-layout-wtf::before { content: "\f466"; } -.bi-life-preserver::before { content: "\f467"; } -.bi-lightbulb-fill::before { content: "\f468"; } -.bi-lightbulb-off-fill::before { content: "\f469"; } -.bi-lightbulb-off::before { content: "\f46a"; } -.bi-lightbulb::before { content: "\f46b"; } -.bi-lightning-charge-fill::before { content: "\f46c"; } -.bi-lightning-charge::before { content: "\f46d"; } -.bi-lightning-fill::before { content: "\f46e"; } -.bi-lightning::before { content: "\f46f"; } -.bi-link-45deg::before { content: "\f470"; } -.bi-link::before { content: "\f471"; } -.bi-linkedin::before { content: "\f472"; } -.bi-list-check::before { content: "\f473"; } -.bi-list-nested::before { content: "\f474"; } -.bi-list-ol::before { content: "\f475"; } -.bi-list-stars::before { content: "\f476"; } -.bi-list-task::before { content: "\f477"; } -.bi-list-ul::before { content: "\f478"; } -.bi-list::before { content: "\f479"; } -.bi-lock-fill::before { content: "\f47a"; } -.bi-lock::before { content: "\f47b"; } -.bi-mailbox::before { content: "\f47c"; } -.bi-mailbox2::before { content: "\f47d"; } -.bi-map-fill::before { content: "\f47e"; } -.bi-map::before { content: "\f47f"; } -.bi-markdown-fill::before { content: "\f480"; } -.bi-markdown::before { content: "\f481"; } -.bi-mask::before { content: "\f482"; } -.bi-megaphone-fill::before { content: "\f483"; } -.bi-megaphone::before { content: "\f484"; } -.bi-menu-app-fill::before { content: "\f485"; } -.bi-menu-app::before { content: "\f486"; } -.bi-menu-button-fill::before { content: "\f487"; } -.bi-menu-button-wide-fill::before { content: "\f488"; } -.bi-menu-button-wide::before { content: "\f489"; } -.bi-menu-button::before { content: "\f48a"; } -.bi-menu-down::before { content: "\f48b"; } -.bi-menu-up::before { content: "\f48c"; } -.bi-mic-fill::before { content: "\f48d"; } -.bi-mic-mute-fill::before { content: "\f48e"; } -.bi-mic-mute::before { content: "\f48f"; } -.bi-mic::before { content: "\f490"; } -.bi-minecart-loaded::before { content: "\f491"; } -.bi-minecart::before { content: "\f492"; } -.bi-moisture::before { content: "\f493"; } -.bi-moon-fill::before { content: "\f494"; } -.bi-moon-stars-fill::before { content: "\f495"; } -.bi-moon-stars::before { content: "\f496"; } -.bi-moon::before { content: "\f497"; } -.bi-mouse-fill::before { content: "\f498"; } -.bi-mouse::before { content: "\f499"; } -.bi-mouse2-fill::before { content: "\f49a"; } -.bi-mouse2::before { content: "\f49b"; } -.bi-mouse3-fill::before { content: "\f49c"; } -.bi-mouse3::before { content: "\f49d"; } -.bi-music-note-beamed::before { content: "\f49e"; } -.bi-music-note-list::before { content: "\f49f"; } -.bi-music-note::before { content: "\f4a0"; } -.bi-music-player-fill::before { content: "\f4a1"; } -.bi-music-player::before { content: "\f4a2"; } -.bi-newspaper::before { content: "\f4a3"; } -.bi-node-minus-fill::before { content: "\f4a4"; } -.bi-node-minus::before { content: "\f4a5"; } -.bi-node-plus-fill::before { content: "\f4a6"; } -.bi-node-plus::before { content: "\f4a7"; } -.bi-nut-fill::before { content: "\f4a8"; } -.bi-nut::before { content: "\f4a9"; } -.bi-octagon-fill::before { content: "\f4aa"; } -.bi-octagon-half::before { content: "\f4ab"; } -.bi-octagon::before { content: "\f4ac"; } -.bi-option::before { content: "\f4ad"; } -.bi-outlet::before { content: "\f4ae"; } -.bi-paint-bucket::before { content: "\f4af"; } -.bi-palette-fill::before { content: "\f4b0"; } -.bi-palette::before { content: "\f4b1"; } -.bi-palette2::before { content: "\f4b2"; } -.bi-paperclip::before { content: "\f4b3"; } -.bi-paragraph::before { content: "\f4b4"; } -.bi-patch-check-fill::before { content: "\f4b5"; } -.bi-patch-check::before { content: "\f4b6"; } -.bi-patch-exclamation-fill::before { content: "\f4b7"; } -.bi-patch-exclamation::before { content: "\f4b8"; } -.bi-patch-minus-fill::before { content: "\f4b9"; } -.bi-patch-minus::before { content: "\f4ba"; } -.bi-patch-plus-fill::before { content: "\f4bb"; } -.bi-patch-plus::before { content: "\f4bc"; } -.bi-patch-question-fill::before { content: "\f4bd"; } -.bi-patch-question::before { content: "\f4be"; } -.bi-pause-btn-fill::before { content: "\f4bf"; } -.bi-pause-btn::before { content: "\f4c0"; } -.bi-pause-circle-fill::before { content: "\f4c1"; } -.bi-pause-circle::before { content: "\f4c2"; } -.bi-pause-fill::before { content: "\f4c3"; } -.bi-pause::before { content: "\f4c4"; } -.bi-peace-fill::before { content: "\f4c5"; } -.bi-peace::before { content: "\f4c6"; } -.bi-pen-fill::before { content: "\f4c7"; } -.bi-pen::before { content: "\f4c8"; } -.bi-pencil-fill::before { content: "\f4c9"; } -.bi-pencil-square::before { content: "\f4ca"; } -.bi-pencil::before { content: "\f4cb"; } -.bi-pentagon-fill::before { content: "\f4cc"; } -.bi-pentagon-half::before { content: "\f4cd"; } -.bi-pentagon::before { content: "\f4ce"; } -.bi-people-fill::before { content: "\f4cf"; } -.bi-people::before { content: "\f4d0"; } -.bi-percent::before { content: "\f4d1"; } -.bi-person-badge-fill::before { content: "\f4d2"; } -.bi-person-badge::before { content: "\f4d3"; } -.bi-person-bounding-box::before { content: "\f4d4"; } -.bi-person-check-fill::before { content: "\f4d5"; } -.bi-person-check::before { content: "\f4d6"; } -.bi-person-circle::before { content: "\f4d7"; } -.bi-person-dash-fill::before { content: "\f4d8"; } -.bi-person-dash::before { content: "\f4d9"; } -.bi-person-fill::before { content: "\f4da"; } -.bi-person-lines-fill::before { content: "\f4db"; } -.bi-person-plus-fill::before { content: "\f4dc"; } -.bi-person-plus::before { content: "\f4dd"; } -.bi-person-square::before { content: "\f4de"; } -.bi-person-x-fill::before { content: "\f4df"; } -.bi-person-x::before { content: "\f4e0"; } -.bi-person::before { content: "\f4e1"; } -.bi-phone-fill::before { content: "\f4e2"; } -.bi-phone-landscape-fill::before { content: "\f4e3"; } -.bi-phone-landscape::before { content: "\f4e4"; } -.bi-phone-vibrate-fill::before { content: "\f4e5"; } -.bi-phone-vibrate::before { content: "\f4e6"; } -.bi-phone::before { content: "\f4e7"; } -.bi-pie-chart-fill::before { content: "\f4e8"; } -.bi-pie-chart::before { content: "\f4e9"; } -.bi-pin-angle-fill::before { content: "\f4ea"; } -.bi-pin-angle::before { content: "\f4eb"; } -.bi-pin-fill::before { content: "\f4ec"; } -.bi-pin::before { content: "\f4ed"; } -.bi-pip-fill::before { content: "\f4ee"; } -.bi-pip::before { content: "\f4ef"; } -.bi-play-btn-fill::before { content: "\f4f0"; } -.bi-play-btn::before { content: "\f4f1"; } -.bi-play-circle-fill::before { content: "\f4f2"; } -.bi-play-circle::before { content: "\f4f3"; } -.bi-play-fill::before { content: "\f4f4"; } -.bi-play::before { content: "\f4f5"; } -.bi-plug-fill::before { content: "\f4f6"; } -.bi-plug::before { content: "\f4f7"; } -.bi-plus-circle-dotted::before { content: "\f4f8"; } -.bi-plus-circle-fill::before { content: "\f4f9"; } -.bi-plus-circle::before { content: "\f4fa"; } -.bi-plus-square-dotted::before { content: "\f4fb"; } -.bi-plus-square-fill::before { content: "\f4fc"; } -.bi-plus-square::before { content: "\f4fd"; } -.bi-plus::before { content: "\f4fe"; } -.bi-power::before { content: "\f4ff"; } -.bi-printer-fill::before { content: "\f500"; } -.bi-printer::before { content: "\f501"; } -.bi-puzzle-fill::before { content: "\f502"; } -.bi-puzzle::before { content: "\f503"; } -.bi-question-circle-fill::before { content: "\f504"; } -.bi-question-circle::before { content: "\f505"; } -.bi-question-diamond-fill::before { content: "\f506"; } -.bi-question-diamond::before { content: "\f507"; } -.bi-question-octagon-fill::before { content: "\f508"; } -.bi-question-octagon::before { content: "\f509"; } -.bi-question-square-fill::before { content: "\f50a"; } -.bi-question-square::before { content: "\f50b"; } -.bi-question::before { content: "\f50c"; } -.bi-rainbow::before { content: "\f50d"; } -.bi-receipt-cutoff::before { content: "\f50e"; } -.bi-receipt::before { content: "\f50f"; } -.bi-reception-0::before { content: "\f510"; } -.bi-reception-1::before { content: "\f511"; } -.bi-reception-2::before { content: "\f512"; } -.bi-reception-3::before { content: "\f513"; } -.bi-reception-4::before { content: "\f514"; } -.bi-record-btn-fill::before { content: "\f515"; } -.bi-record-btn::before { content: "\f516"; } -.bi-record-circle-fill::before { content: "\f517"; } -.bi-record-circle::before { content: "\f518"; } -.bi-record-fill::before { content: "\f519"; } -.bi-record::before { content: "\f51a"; } -.bi-record2-fill::before { content: "\f51b"; } -.bi-record2::before { content: "\f51c"; } -.bi-reply-all-fill::before { content: "\f51d"; } -.bi-reply-all::before { content: "\f51e"; } -.bi-reply-fill::before { content: "\f51f"; } -.bi-reply::before { content: "\f520"; } -.bi-rss-fill::before { content: "\f521"; } -.bi-rss::before { content: "\f522"; } -.bi-rulers::before { content: "\f523"; } -.bi-save-fill::before { content: "\f524"; } -.bi-save::before { content: "\f525"; } -.bi-save2-fill::before { content: "\f526"; } -.bi-save2::before { content: "\f527"; } -.bi-scissors::before { content: "\f528"; } -.bi-screwdriver::before { content: "\f529"; } -.bi-search::before { content: "\f52a"; } -.bi-segmented-nav::before { content: "\f52b"; } -.bi-server::before { content: "\f52c"; } -.bi-share-fill::before { content: "\f52d"; } -.bi-share::before { content: "\f52e"; } -.bi-shield-check::before { content: "\f52f"; } -.bi-shield-exclamation::before { content: "\f530"; } -.bi-shield-fill-check::before { content: "\f531"; } -.bi-shield-fill-exclamation::before { content: "\f532"; } -.bi-shield-fill-minus::before { content: "\f533"; } -.bi-shield-fill-plus::before { content: "\f534"; } -.bi-shield-fill-x::before { content: "\f535"; } -.bi-shield-fill::before { content: "\f536"; } -.bi-shield-lock-fill::before { content: "\f537"; } -.bi-shield-lock::before { content: "\f538"; } -.bi-shield-minus::before { content: "\f539"; } -.bi-shield-plus::before { content: "\f53a"; } -.bi-shield-shaded::before { content: "\f53b"; } -.bi-shield-slash-fill::before { content: "\f53c"; } -.bi-shield-slash::before { content: "\f53d"; } -.bi-shield-x::before { content: "\f53e"; } -.bi-shield::before { content: "\f53f"; } -.bi-shift-fill::before { content: "\f540"; } -.bi-shift::before { content: "\f541"; } -.bi-shop-window::before { content: "\f542"; } -.bi-shop::before { content: "\f543"; } -.bi-shuffle::before { content: "\f544"; } -.bi-signpost-2-fill::before { content: "\f545"; } -.bi-signpost-2::before { content: "\f546"; } -.bi-signpost-fill::before { content: "\f547"; } -.bi-signpost-split-fill::before { content: "\f548"; } -.bi-signpost-split::before { content: "\f549"; } -.bi-signpost::before { content: "\f54a"; } -.bi-sim-fill::before { content: "\f54b"; } -.bi-sim::before { content: "\f54c"; } -.bi-skip-backward-btn-fill::before { content: "\f54d"; } -.bi-skip-backward-btn::before { content: "\f54e"; } -.bi-skip-backward-circle-fill::before { content: "\f54f"; } -.bi-skip-backward-circle::before { content: "\f550"; } -.bi-skip-backward-fill::before { content: "\f551"; } -.bi-skip-backward::before { content: "\f552"; } -.bi-skip-end-btn-fill::before { content: "\f553"; } -.bi-skip-end-btn::before { content: "\f554"; } -.bi-skip-end-circle-fill::before { content: "\f555"; } -.bi-skip-end-circle::before { content: "\f556"; } -.bi-skip-end-fill::before { content: "\f557"; } -.bi-skip-end::before { content: "\f558"; } -.bi-skip-forward-btn-fill::before { content: "\f559"; } -.bi-skip-forward-btn::before { content: "\f55a"; } -.bi-skip-forward-circle-fill::before { content: "\f55b"; } -.bi-skip-forward-circle::before { content: "\f55c"; } -.bi-skip-forward-fill::before { content: "\f55d"; } -.bi-skip-forward::before { content: "\f55e"; } -.bi-skip-start-btn-fill::before { content: "\f55f"; } -.bi-skip-start-btn::before { content: "\f560"; } -.bi-skip-start-circle-fill::before { content: "\f561"; } -.bi-skip-start-circle::before { content: "\f562"; } -.bi-skip-start-fill::before { content: "\f563"; } -.bi-skip-start::before { content: "\f564"; } -.bi-slack::before { content: "\f565"; } -.bi-slash-circle-fill::before { content: "\f566"; } -.bi-slash-circle::before { content: "\f567"; } -.bi-slash-square-fill::before { content: "\f568"; } -.bi-slash-square::before { content: "\f569"; } -.bi-slash::before { content: "\f56a"; } -.bi-sliders::before { content: "\f56b"; } -.bi-smartwatch::before { content: "\f56c"; } -.bi-snow::before { content: "\f56d"; } -.bi-snow2::before { content: "\f56e"; } -.bi-snow3::before { content: "\f56f"; } -.bi-sort-alpha-down-alt::before { content: "\f570"; } -.bi-sort-alpha-down::before { content: "\f571"; } -.bi-sort-alpha-up-alt::before { content: "\f572"; } -.bi-sort-alpha-up::before { content: "\f573"; } -.bi-sort-down-alt::before { content: "\f574"; } -.bi-sort-down::before { content: "\f575"; } -.bi-sort-numeric-down-alt::before { content: "\f576"; } -.bi-sort-numeric-down::before { content: "\f577"; } -.bi-sort-numeric-up-alt::before { content: "\f578"; } -.bi-sort-numeric-up::before { content: "\f579"; } -.bi-sort-up-alt::before { content: "\f57a"; } -.bi-sort-up::before { content: "\f57b"; } -.bi-soundwave::before { content: "\f57c"; } -.bi-speaker-fill::before { content: "\f57d"; } -.bi-speaker::before { content: "\f57e"; } -.bi-speedometer::before { content: "\f57f"; } -.bi-speedometer2::before { content: "\f580"; } -.bi-spellcheck::before { content: "\f581"; } -.bi-square-fill::before { content: "\f582"; } -.bi-square-half::before { content: "\f583"; } -.bi-square::before { content: "\f584"; } -.bi-stack::before { content: "\f585"; } -.bi-star-fill::before { content: "\f586"; } -.bi-star-half::before { content: "\f587"; } -.bi-star::before { content: "\f588"; } -.bi-stars::before { content: "\f589"; } -.bi-stickies-fill::before { content: "\f58a"; } -.bi-stickies::before { content: "\f58b"; } -.bi-sticky-fill::before { content: "\f58c"; } -.bi-sticky::before { content: "\f58d"; } -.bi-stop-btn-fill::before { content: "\f58e"; } -.bi-stop-btn::before { content: "\f58f"; } -.bi-stop-circle-fill::before { content: "\f590"; } -.bi-stop-circle::before { content: "\f591"; } -.bi-stop-fill::before { content: "\f592"; } -.bi-stop::before { content: "\f593"; } -.bi-stoplights-fill::before { content: "\f594"; } -.bi-stoplights::before { content: "\f595"; } -.bi-stopwatch-fill::before { content: "\f596"; } -.bi-stopwatch::before { content: "\f597"; } -.bi-subtract::before { content: "\f598"; } -.bi-suit-club-fill::before { content: "\f599"; } -.bi-suit-club::before { content: "\f59a"; } -.bi-suit-diamond-fill::before { content: "\f59b"; } -.bi-suit-diamond::before { content: "\f59c"; } -.bi-suit-heart-fill::before { content: "\f59d"; } -.bi-suit-heart::before { content: "\f59e"; } -.bi-suit-spade-fill::before { content: "\f59f"; } -.bi-suit-spade::before { content: "\f5a0"; } -.bi-sun-fill::before { content: "\f5a1"; } -.bi-sun::before { content: "\f5a2"; } -.bi-sunglasses::before { content: "\f5a3"; } -.bi-sunrise-fill::before { content: "\f5a4"; } -.bi-sunrise::before { content: "\f5a5"; } -.bi-sunset-fill::before { content: "\f5a6"; } -.bi-sunset::before { content: "\f5a7"; } -.bi-symmetry-horizontal::before { content: "\f5a8"; } -.bi-symmetry-vertical::before { content: "\f5a9"; } -.bi-table::before { content: "\f5aa"; } -.bi-tablet-fill::before { content: "\f5ab"; } -.bi-tablet-landscape-fill::before { content: "\f5ac"; } -.bi-tablet-landscape::before { content: "\f5ad"; } -.bi-tablet::before { content: "\f5ae"; } -.bi-tag-fill::before { content: "\f5af"; } -.bi-tag::before { content: "\f5b0"; } -.bi-tags-fill::before { content: "\f5b1"; } -.bi-tags::before { content: "\f5b2"; } -.bi-telegram::before { content: "\f5b3"; } -.bi-telephone-fill::before { content: "\f5b4"; } -.bi-telephone-forward-fill::before { content: "\f5b5"; } -.bi-telephone-forward::before { content: "\f5b6"; } -.bi-telephone-inbound-fill::before { content: "\f5b7"; } -.bi-telephone-inbound::before { content: "\f5b8"; } -.bi-telephone-minus-fill::before { content: "\f5b9"; } -.bi-telephone-minus::before { content: "\f5ba"; } -.bi-telephone-outbound-fill::before { content: "\f5bb"; } -.bi-telephone-outbound::before { content: "\f5bc"; } -.bi-telephone-plus-fill::before { content: "\f5bd"; } -.bi-telephone-plus::before { content: "\f5be"; } -.bi-telephone-x-fill::before { content: "\f5bf"; } -.bi-telephone-x::before { content: "\f5c0"; } -.bi-telephone::before { content: "\f5c1"; } -.bi-terminal-fill::before { content: "\f5c2"; } -.bi-terminal::before { content: "\f5c3"; } -.bi-text-center::before { content: "\f5c4"; } -.bi-text-indent-left::before { content: "\f5c5"; } -.bi-text-indent-right::before { content: "\f5c6"; } -.bi-text-left::before { content: "\f5c7"; } -.bi-text-paragraph::before { content: "\f5c8"; } -.bi-text-right::before { content: "\f5c9"; } -.bi-textarea-resize::before { content: "\f5ca"; } -.bi-textarea-t::before { content: "\f5cb"; } -.bi-textarea::before { content: "\f5cc"; } -.bi-thermometer-half::before { content: "\f5cd"; } -.bi-thermometer-high::before { content: "\f5ce"; } -.bi-thermometer-low::before { content: "\f5cf"; } -.bi-thermometer-snow::before { content: "\f5d0"; } -.bi-thermometer-sun::before { content: "\f5d1"; } -.bi-thermometer::before { content: "\f5d2"; } -.bi-three-dots-vertical::before { content: "\f5d3"; } -.bi-three-dots::before { content: "\f5d4"; } -.bi-toggle-off::before { content: "\f5d5"; } -.bi-toggle-on::before { content: "\f5d6"; } -.bi-toggle2-off::before { content: "\f5d7"; } -.bi-toggle2-on::before { content: "\f5d8"; } -.bi-toggles::before { content: "\f5d9"; } -.bi-toggles2::before { content: "\f5da"; } -.bi-tools::before { content: "\f5db"; } -.bi-tornado::before { content: "\f5dc"; } -.bi-trash-fill::before { content: "\f5dd"; } -.bi-trash::before { content: "\f5de"; } -.bi-trash2-fill::before { content: "\f5df"; } -.bi-trash2::before { content: "\f5e0"; } -.bi-tree-fill::before { content: "\f5e1"; } -.bi-tree::before { content: "\f5e2"; } -.bi-triangle-fill::before { content: "\f5e3"; } -.bi-triangle-half::before { content: "\f5e4"; } -.bi-triangle::before { content: "\f5e5"; } -.bi-trophy-fill::before { content: "\f5e6"; } -.bi-trophy::before { content: "\f5e7"; } -.bi-tropical-storm::before { content: "\f5e8"; } -.bi-truck-flatbed::before { content: "\f5e9"; } -.bi-truck::before { content: "\f5ea"; } -.bi-tsunami::before { content: "\f5eb"; } -.bi-tv-fill::before { content: "\f5ec"; } -.bi-tv::before { content: "\f5ed"; } -.bi-twitch::before { content: "\f5ee"; } -.bi-twitter::before { content: "\f5ef"; } -.bi-type-bold::before { content: "\f5f0"; } -.bi-type-h1::before { content: "\f5f1"; } -.bi-type-h2::before { content: "\f5f2"; } -.bi-type-h3::before { content: "\f5f3"; } -.bi-type-italic::before { content: "\f5f4"; } -.bi-type-strikethrough::before { content: "\f5f5"; } -.bi-type-underline::before { content: "\f5f6"; } -.bi-type::before { content: "\f5f7"; } -.bi-ui-checks-grid::before { content: "\f5f8"; } -.bi-ui-checks::before { content: "\f5f9"; } -.bi-ui-radios-grid::before { content: "\f5fa"; } -.bi-ui-radios::before { content: "\f5fb"; } -.bi-umbrella-fill::before { content: "\f5fc"; } -.bi-umbrella::before { content: "\f5fd"; } -.bi-union::before { content: "\f5fe"; } -.bi-unlock-fill::before { content: "\f5ff"; } -.bi-unlock::before { content: "\f600"; } -.bi-upc-scan::before { content: "\f601"; } -.bi-upc::before { content: "\f602"; } -.bi-upload::before { content: "\f603"; } -.bi-vector-pen::before { content: "\f604"; } -.bi-view-list::before { content: "\f605"; } -.bi-view-stacked::before { content: "\f606"; } -.bi-vinyl-fill::before { content: "\f607"; } -.bi-vinyl::before { content: "\f608"; } -.bi-voicemail::before { content: "\f609"; } -.bi-volume-down-fill::before { content: "\f60a"; } -.bi-volume-down::before { content: "\f60b"; } -.bi-volume-mute-fill::before { content: "\f60c"; } -.bi-volume-mute::before { content: "\f60d"; } -.bi-volume-off-fill::before { content: "\f60e"; } -.bi-volume-off::before { content: "\f60f"; } -.bi-volume-up-fill::before { content: "\f610"; } -.bi-volume-up::before { content: "\f611"; } -.bi-vr::before { content: "\f612"; } -.bi-wallet-fill::before { content: "\f613"; } -.bi-wallet::before { content: "\f614"; } -.bi-wallet2::before { content: "\f615"; } -.bi-watch::before { content: "\f616"; } -.bi-water::before { content: "\f617"; } -.bi-whatsapp::before { content: "\f618"; } -.bi-wifi-1::before { content: "\f619"; } -.bi-wifi-2::before { content: "\f61a"; } -.bi-wifi-off::before { content: "\f61b"; } -.bi-wifi::before { content: "\f61c"; } -.bi-wind::before { content: "\f61d"; } -.bi-window-dock::before { content: "\f61e"; } -.bi-window-sidebar::before { content: "\f61f"; } -.bi-window::before { content: "\f620"; } -.bi-wrench::before { content: "\f621"; } -.bi-x-circle-fill::before { content: "\f622"; } -.bi-x-circle::before { content: "\f623"; } -.bi-x-diamond-fill::before { content: "\f624"; } -.bi-x-diamond::before { content: "\f625"; } -.bi-x-octagon-fill::before { content: "\f626"; } -.bi-x-octagon::before { content: "\f627"; } -.bi-x-square-fill::before { content: "\f628"; } -.bi-x-square::before { content: "\f629"; } -.bi-x::before { content: "\f62a"; } -.bi-youtube::before { content: "\f62b"; } -.bi-zoom-in::before { content: "\f62c"; } -.bi-zoom-out::before { content: "\f62d"; } -.bi-bank::before { content: "\f62e"; } -.bi-bank2::before { content: "\f62f"; } -.bi-bell-slash-fill::before { content: "\f630"; } -.bi-bell-slash::before { content: "\f631"; } -.bi-cash-coin::before { content: "\f632"; } -.bi-check-lg::before { content: "\f633"; } -.bi-coin::before { content: "\f634"; } -.bi-currency-bitcoin::before { content: "\f635"; } -.bi-currency-dollar::before { content: "\f636"; } -.bi-currency-euro::before { content: "\f637"; } -.bi-currency-exchange::before { content: "\f638"; } -.bi-currency-pound::before { content: "\f639"; } -.bi-currency-yen::before { content: "\f63a"; } -.bi-dash-lg::before { content: "\f63b"; } -.bi-exclamation-lg::before { content: "\f63c"; } -.bi-file-earmark-pdf-fill::before { content: "\f63d"; } -.bi-file-earmark-pdf::before { content: "\f63e"; } -.bi-file-pdf-fill::before { content: "\f63f"; } -.bi-file-pdf::before { content: "\f640"; } -.bi-gender-ambiguous::before { content: "\f641"; } -.bi-gender-female::before { content: "\f642"; } -.bi-gender-male::before { content: "\f643"; } -.bi-gender-trans::before { content: "\f644"; } -.bi-headset-vr::before { content: "\f645"; } -.bi-info-lg::before { content: "\f646"; } -.bi-mastodon::before { content: "\f647"; } -.bi-messenger::before { content: "\f648"; } -.bi-piggy-bank-fill::before { content: "\f649"; } -.bi-piggy-bank::before { content: "\f64a"; } -.bi-pin-map-fill::before { content: "\f64b"; } -.bi-pin-map::before { content: "\f64c"; } -.bi-plus-lg::before { content: "\f64d"; } -.bi-question-lg::before { content: "\f64e"; } -.bi-recycle::before { content: "\f64f"; } -.bi-reddit::before { content: "\f650"; } -.bi-safe-fill::before { content: "\f651"; } -.bi-safe2-fill::before { content: "\f652"; } -.bi-safe2::before { content: "\f653"; } -.bi-sd-card-fill::before { content: "\f654"; } -.bi-sd-card::before { content: "\f655"; } -.bi-skype::before { content: "\f656"; } -.bi-slash-lg::before { content: "\f657"; } -.bi-translate::before { content: "\f658"; } -.bi-x-lg::before { content: "\f659"; } -.bi-safe::before { content: "\f65a"; } -.bi-apple::before { content: "\f65b"; } -.bi-microsoft::before { content: "\f65d"; } -.bi-windows::before { content: "\f65e"; } -.bi-behance::before { content: "\f65c"; } -.bi-dribbble::before { content: "\f65f"; } -.bi-line::before { content: "\f660"; } -.bi-medium::before { content: "\f661"; } -.bi-paypal::before { content: "\f662"; } -.bi-pinterest::before { content: "\f663"; } -.bi-signal::before { content: "\f664"; } -.bi-snapchat::before { content: "\f665"; } -.bi-spotify::before { content: "\f666"; } -.bi-stack-overflow::before { content: "\f667"; } -.bi-strava::before { content: "\f668"; } -.bi-wordpress::before { content: "\f669"; } -.bi-vimeo::before { content: "\f66a"; } -.bi-activity::before { content: "\f66b"; } -.bi-easel2-fill::before { content: "\f66c"; } -.bi-easel2::before { content: "\f66d"; } -.bi-easel3-fill::before { content: "\f66e"; } -.bi-easel3::before { content: "\f66f"; } -.bi-fan::before { content: "\f670"; } -.bi-fingerprint::before { content: "\f671"; } -.bi-graph-down-arrow::before { content: "\f672"; } -.bi-graph-up-arrow::before { content: "\f673"; } -.bi-hypnotize::before { content: "\f674"; } -.bi-magic::before { content: "\f675"; } -.bi-person-rolodex::before { content: "\f676"; } -.bi-person-video::before { content: "\f677"; } -.bi-person-video2::before { content: "\f678"; } -.bi-person-video3::before { content: "\f679"; } -.bi-person-workspace::before { content: "\f67a"; } -.bi-radioactive::before { content: "\f67b"; } -.bi-webcam-fill::before { content: "\f67c"; } -.bi-webcam::before { content: "\f67d"; } -.bi-yin-yang::before { content: "\f67e"; } -.bi-bandaid-fill::before { content: "\f680"; } -.bi-bandaid::before { content: "\f681"; } -.bi-bluetooth::before { content: "\f682"; } -.bi-body-text::before { content: "\f683"; } -.bi-boombox::before { content: "\f684"; } -.bi-boxes::before { content: "\f685"; } -.bi-dpad-fill::before { content: "\f686"; } -.bi-dpad::before { content: "\f687"; } -.bi-ear-fill::before { content: "\f688"; } -.bi-ear::before { content: "\f689"; } -.bi-envelope-check-1::before { content: "\f68a"; } -.bi-envelope-check-fill::before { content: "\f68b"; } -.bi-envelope-check::before { content: "\f68c"; } -.bi-envelope-dash-1::before { content: "\f68d"; } -.bi-envelope-dash-fill::before { content: "\f68e"; } -.bi-envelope-dash::before { content: "\f68f"; } -.bi-envelope-exclamation-1::before { content: "\f690"; } -.bi-envelope-exclamation-fill::before { content: "\f691"; } -.bi-envelope-exclamation::before { content: "\f692"; } -.bi-envelope-plus-fill::before { content: "\f693"; } -.bi-envelope-plus::before { content: "\f694"; } -.bi-envelope-slash-1::before { content: "\f695"; } -.bi-envelope-slash-fill::before { content: "\f696"; } -.bi-envelope-slash::before { content: "\f697"; } -.bi-envelope-x-1::before { content: "\f698"; } -.bi-envelope-x-fill::before { content: "\f699"; } -.bi-envelope-x::before { content: "\f69a"; } -.bi-explicit-fill::before { content: "\f69b"; } -.bi-explicit::before { content: "\f69c"; } -.bi-git::before { content: "\f69d"; } -.bi-infinity::before { content: "\f69e"; } -.bi-list-columns-reverse::before { content: "\f69f"; } -.bi-list-columns::before { content: "\f6a0"; } -.bi-meta::before { content: "\f6a1"; } -.bi-mortorboard-fill::before { content: "\f6a2"; } -.bi-mortorboard::before { content: "\f6a3"; } -.bi-nintendo-switch::before { content: "\f6a4"; } -.bi-pc-display-horizontal::before { content: "\f6a5"; } -.bi-pc-display::before { content: "\f6a6"; } -.bi-pc-horizontal::before { content: "\f6a7"; } -.bi-pc::before { content: "\f6a8"; } -.bi-playstation::before { content: "\f6a9"; } -.bi-plus-slash-minus::before { content: "\f6aa"; } -.bi-projector-fill::before { content: "\f6ab"; } -.bi-projector::before { content: "\f6ac"; } -.bi-qr-code-scan::before { content: "\f6ad"; } -.bi-qr-code::before { content: "\f6ae"; } -.bi-quora::before { content: "\f6af"; } -.bi-quote::before { content: "\f6b0"; } -.bi-robot::before { content: "\f6b1"; } -.bi-send-check-fill::before { content: "\f6b2"; } -.bi-send-check::before { content: "\f6b3"; } -.bi-send-dash-fill::before { content: "\f6b4"; } -.bi-send-dash::before { content: "\f6b5"; } -.bi-send-exclamation-1::before { content: "\f6b6"; } -.bi-send-exclamation-fill::before { content: "\f6b7"; } -.bi-send-exclamation::before { content: "\f6b8"; } -.bi-send-fill::before { content: "\f6b9"; } -.bi-send-plus-fill::before { content: "\f6ba"; } -.bi-send-plus::before { content: "\f6bb"; } -.bi-send-slash-fill::before { content: "\f6bc"; } -.bi-send-slash::before { content: "\f6bd"; } -.bi-send-x-fill::before { content: "\f6be"; } -.bi-send-x::before { content: "\f6bf"; } -.bi-send::before { content: "\f6c0"; } -.bi-steam::before { content: "\f6c1"; } -.bi-terminal-dash-1::before { content: "\f6c2"; } -.bi-terminal-dash::before { content: "\f6c3"; } -.bi-terminal-plus::before { content: "\f6c4"; } -.bi-terminal-split::before { content: "\f6c5"; } -.bi-ticket-detailed-fill::before { content: "\f6c6"; } -.bi-ticket-detailed::before { content: "\f6c7"; } -.bi-ticket-fill::before { content: "\f6c8"; } -.bi-ticket-perforated-fill::before { content: "\f6c9"; } -.bi-ticket-perforated::before { content: "\f6ca"; } -.bi-ticket::before { content: "\f6cb"; } -.bi-tiktok::before { content: "\f6cc"; } -.bi-window-dash::before { content: "\f6cd"; } -.bi-window-desktop::before { content: "\f6ce"; } -.bi-window-fullscreen::before { content: "\f6cf"; } -.bi-window-plus::before { content: "\f6d0"; } -.bi-window-split::before { content: "\f6d1"; } -.bi-window-stack::before { content: "\f6d2"; } -.bi-window-x::before { content: "\f6d3"; } -.bi-xbox::before { content: "\f6d4"; } -.bi-ethernet::before { content: "\f6d5"; } -.bi-hdmi-fill::before { content: "\f6d6"; } -.bi-hdmi::before { content: "\f6d7"; } -.bi-usb-c-fill::before { content: "\f6d8"; } -.bi-usb-c::before { content: "\f6d9"; } -.bi-usb-fill::before { content: "\f6da"; } -.bi-usb-plug-fill::before { content: "\f6db"; } -.bi-usb-plug::before { content: "\f6dc"; } -.bi-usb-symbol::before { content: "\f6dd"; } -.bi-usb::before { content: "\f6de"; } -.bi-boombox-fill::before { content: "\f6df"; } -.bi-displayport-1::before { content: "\f6e0"; } -.bi-displayport::before { content: "\f6e1"; } -.bi-gpu-card::before { content: "\f6e2"; } -.bi-memory::before { content: "\f6e3"; } -.bi-modem-fill::before { content: "\f6e4"; } -.bi-modem::before { content: "\f6e5"; } -.bi-motherboard-fill::before { content: "\f6e6"; } -.bi-motherboard::before { content: "\f6e7"; } -.bi-optical-audio-fill::before { content: "\f6e8"; } -.bi-optical-audio::before { content: "\f6e9"; } -.bi-pci-card::before { content: "\f6ea"; } -.bi-router-fill::before { content: "\f6eb"; } -.bi-router::before { content: "\f6ec"; } -.bi-ssd-fill::before { content: "\f6ed"; } -.bi-ssd::before { content: "\f6ee"; } -.bi-thunderbolt-fill::before { content: "\f6ef"; } -.bi-thunderbolt::before { content: "\f6f0"; } -.bi-usb-drive-fill::before { content: "\f6f1"; } -.bi-usb-drive::before { content: "\f6f2"; } -.bi-usb-micro-fill::before { content: "\f6f3"; } -.bi-usb-micro::before { content: "\f6f4"; } -.bi-usb-mini-fill::before { content: "\f6f5"; } -.bi-usb-mini::before { content: "\f6f6"; } -.bi-cloud-haze2::before { content: "\f6f7"; } -.bi-device-hdd-fill::before { content: "\f6f8"; } -.bi-device-hdd::before { content: "\f6f9"; } -.bi-device-ssd-fill::before { content: "\f6fa"; } -.bi-device-ssd::before { content: "\f6fb"; } -.bi-displayport-fill::before { content: "\f6fc"; } -.bi-mortarboard-fill::before { content: "\f6fd"; } -.bi-mortarboard::before { content: "\f6fe"; } -.bi-terminal-x::before { content: "\f6ff"; } -.bi-arrow-through-heart-fill::before { content: "\f700"; } -.bi-arrow-through-heart::before { content: "\f701"; } -.bi-badge-sd-fill::before { content: "\f702"; } -.bi-badge-sd::before { content: "\f703"; } -.bi-bag-heart-fill::before { content: "\f704"; } -.bi-bag-heart::before { content: "\f705"; } -.bi-balloon-fill::before { content: "\f706"; } -.bi-balloon-heart-fill::before { content: "\f707"; } -.bi-balloon-heart::before { content: "\f708"; } -.bi-balloon::before { content: "\f709"; } -.bi-box2-fill::before { content: "\f70a"; } -.bi-box2-heart-fill::before { content: "\f70b"; } -.bi-box2-heart::before { content: "\f70c"; } -.bi-box2::before { content: "\f70d"; } -.bi-braces-asterisk::before { content: "\f70e"; } -.bi-calendar-heart-fill::before { content: "\f70f"; } -.bi-calendar-heart::before { content: "\f710"; } -.bi-calendar2-heart-fill::before { content: "\f711"; } -.bi-calendar2-heart::before { content: "\f712"; } -.bi-chat-heart-fill::before { content: "\f713"; } -.bi-chat-heart::before { content: "\f714"; } -.bi-chat-left-heart-fill::before { content: "\f715"; } -.bi-chat-left-heart::before { content: "\f716"; } -.bi-chat-right-heart-fill::before { content: "\f717"; } -.bi-chat-right-heart::before { content: "\f718"; } -.bi-chat-square-heart-fill::before { content: "\f719"; } -.bi-chat-square-heart::before { content: "\f71a"; } -.bi-clipboard-check-fill::before { content: "\f71b"; } -.bi-clipboard-data-fill::before { content: "\f71c"; } -.bi-clipboard-fill::before { content: "\f71d"; } -.bi-clipboard-heart-fill::before { content: "\f71e"; } -.bi-clipboard-heart::before { content: "\f71f"; } -.bi-clipboard-minus-fill::before { content: "\f720"; } -.bi-clipboard-plus-fill::before { content: "\f721"; } -.bi-clipboard-pulse::before { content: "\f722"; } -.bi-clipboard-x-fill::before { content: "\f723"; } -.bi-clipboard2-check-fill::before { content: "\f724"; } -.bi-clipboard2-check::before { content: "\f725"; } -.bi-clipboard2-data-fill::before { content: "\f726"; } -.bi-clipboard2-data::before { content: "\f727"; } -.bi-clipboard2-fill::before { content: "\f728"; } -.bi-clipboard2-heart-fill::before { content: "\f729"; } -.bi-clipboard2-heart::before { content: "\f72a"; } -.bi-clipboard2-minus-fill::before { content: "\f72b"; } -.bi-clipboard2-minus::before { content: "\f72c"; } -.bi-clipboard2-plus-fill::before { content: "\f72d"; } -.bi-clipboard2-plus::before { content: "\f72e"; } -.bi-clipboard2-pulse-fill::before { content: "\f72f"; } -.bi-clipboard2-pulse::before { content: "\f730"; } -.bi-clipboard2-x-fill::before { content: "\f731"; } -.bi-clipboard2-x::before { content: "\f732"; } -.bi-clipboard2::before { content: "\f733"; } -.bi-emoji-kiss-fill::before { content: "\f734"; } -.bi-emoji-kiss::before { content: "\f735"; } -.bi-envelope-heart-fill::before { content: "\f736"; } -.bi-envelope-heart::before { content: "\f737"; } -.bi-envelope-open-heart-fill::before { content: "\f738"; } -.bi-envelope-open-heart::before { content: "\f739"; } -.bi-envelope-paper-fill::before { content: "\f73a"; } -.bi-envelope-paper-heart-fill::before { content: "\f73b"; } -.bi-envelope-paper-heart::before { content: "\f73c"; } -.bi-envelope-paper::before { content: "\f73d"; } -.bi-filetype-aac::before { content: "\f73e"; } -.bi-filetype-ai::before { content: "\f73f"; } -.bi-filetype-bmp::before { content: "\f740"; } -.bi-filetype-cs::before { content: "\f741"; } -.bi-filetype-css::before { content: "\f742"; } -.bi-filetype-csv::before { content: "\f743"; } -.bi-filetype-doc::before { content: "\f744"; } -.bi-filetype-docx::before { content: "\f745"; } -.bi-filetype-exe::before { content: "\f746"; } -.bi-filetype-gif::before { content: "\f747"; } -.bi-filetype-heic::before { content: "\f748"; } -.bi-filetype-html::before { content: "\f749"; } -.bi-filetype-java::before { content: "\f74a"; } -.bi-filetype-jpg::before { content: "\f74b"; } -.bi-filetype-js::before { content: "\f74c"; } -.bi-filetype-jsx::before { content: "\f74d"; } -.bi-filetype-key::before { content: "\f74e"; } -.bi-filetype-m4p::before { content: "\f74f"; } -.bi-filetype-md::before { content: "\f750"; } -.bi-filetype-mdx::before { content: "\f751"; } -.bi-filetype-mov::before { content: "\f752"; } -.bi-filetype-mp3::before { content: "\f753"; } -.bi-filetype-mp4::before { content: "\f754"; } -.bi-filetype-otf::before { content: "\f755"; } -.bi-filetype-pdf::before { content: "\f756"; } -.bi-filetype-php::before { content: "\f757"; } -.bi-filetype-png::before { content: "\f758"; } -.bi-filetype-ppt-1::before { content: "\f759"; } -.bi-filetype-ppt::before { content: "\f75a"; } -.bi-filetype-psd::before { content: "\f75b"; } -.bi-filetype-py::before { content: "\f75c"; } -.bi-filetype-raw::before { content: "\f75d"; } -.bi-filetype-rb::before { content: "\f75e"; } -.bi-filetype-sass::before { content: "\f75f"; } -.bi-filetype-scss::before { content: "\f760"; } -.bi-filetype-sh::before { content: "\f761"; } -.bi-filetype-svg::before { content: "\f762"; } -.bi-filetype-tiff::before { content: "\f763"; } -.bi-filetype-tsx::before { content: "\f764"; } -.bi-filetype-ttf::before { content: "\f765"; } -.bi-filetype-txt::before { content: "\f766"; } -.bi-filetype-wav::before { content: "\f767"; } -.bi-filetype-woff::before { content: "\f768"; } -.bi-filetype-xls-1::before { content: "\f769"; } -.bi-filetype-xls::before { content: "\f76a"; } -.bi-filetype-xml::before { content: "\f76b"; } -.bi-filetype-yml::before { content: "\f76c"; } -.bi-heart-arrow::before { content: "\f76d"; } -.bi-heart-pulse-fill::before { content: "\f76e"; } -.bi-heart-pulse::before { content: "\f76f"; } -.bi-heartbreak-fill::before { content: "\f770"; } -.bi-heartbreak::before { content: "\f771"; } -.bi-hearts::before { content: "\f772"; } -.bi-hospital-fill::before { content: "\f773"; } -.bi-hospital::before { content: "\f774"; } -.bi-house-heart-fill::before { content: "\f775"; } -.bi-house-heart::before { content: "\f776"; } -.bi-incognito::before { content: "\f777"; } -.bi-magnet-fill::before { content: "\f778"; } -.bi-magnet::before { content: "\f779"; } -.bi-person-heart::before { content: "\f77a"; } -.bi-person-hearts::before { content: "\f77b"; } -.bi-phone-flip::before { content: "\f77c"; } -.bi-plugin::before { content: "\f77d"; } -.bi-postage-fill::before { content: "\f77e"; } -.bi-postage-heart-fill::before { content: "\f77f"; } -.bi-postage-heart::before { content: "\f780"; } -.bi-postage::before { content: "\f781"; } -.bi-postcard-fill::before { content: "\f782"; } -.bi-postcard-heart-fill::before { content: "\f783"; } -.bi-postcard-heart::before { content: "\f784"; } -.bi-postcard::before { content: "\f785"; } -.bi-search-heart-fill::before { content: "\f786"; } -.bi-search-heart::before { content: "\f787"; } -.bi-sliders2-vertical::before { content: "\f788"; } -.bi-sliders2::before { content: "\f789"; } -.bi-trash3-fill::before { content: "\f78a"; } -.bi-trash3::before { content: "\f78b"; } -.bi-valentine::before { content: "\f78c"; } -.bi-valentine2::before { content: "\f78d"; } -.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } -.bi-wrench-adjustable-circle::before { content: "\f78f"; } -.bi-wrench-adjustable::before { content: "\f790"; } -.bi-filetype-json::before { content: "\f791"; } -.bi-filetype-pptx::before { content: "\f792"; } -.bi-filetype-xlsx::before { content: "\f793"; } -.bi-1-circle-1::before { content: "\f794"; } -.bi-1-circle-fill-1::before { content: "\f795"; } -.bi-1-circle-fill::before { content: "\f796"; } -.bi-1-circle::before { content: "\f797"; } -.bi-1-square-fill::before { content: "\f798"; } -.bi-1-square::before { content: "\f799"; } -.bi-2-circle-1::before { content: "\f79a"; } -.bi-2-circle-fill-1::before { content: "\f79b"; } -.bi-2-circle-fill::before { content: "\f79c"; } -.bi-2-circle::before { content: "\f79d"; } -.bi-2-square-fill::before { content: "\f79e"; } -.bi-2-square::before { content: "\f79f"; } -.bi-3-circle-1::before { content: "\f7a0"; } -.bi-3-circle-fill-1::before { content: "\f7a1"; } -.bi-3-circle-fill::before { content: "\f7a2"; } -.bi-3-circle::before { content: "\f7a3"; } -.bi-3-square-fill::before { content: "\f7a4"; } -.bi-3-square::before { content: "\f7a5"; } -.bi-4-circle-1::before { content: "\f7a6"; } -.bi-4-circle-fill-1::before { content: "\f7a7"; } -.bi-4-circle-fill::before { content: "\f7a8"; } -.bi-4-circle::before { content: "\f7a9"; } -.bi-4-square-fill::before { content: "\f7aa"; } -.bi-4-square::before { content: "\f7ab"; } -.bi-5-circle-1::before { content: "\f7ac"; } -.bi-5-circle-fill-1::before { content: "\f7ad"; } -.bi-5-circle-fill::before { content: "\f7ae"; } -.bi-5-circle::before { content: "\f7af"; } -.bi-5-square-fill::before { content: "\f7b0"; } -.bi-5-square::before { content: "\f7b1"; } -.bi-6-circle-1::before { content: "\f7b2"; } -.bi-6-circle-fill-1::before { content: "\f7b3"; } -.bi-6-circle-fill::before { content: "\f7b4"; } -.bi-6-circle::before { content: "\f7b5"; } -.bi-6-square-fill::before { content: "\f7b6"; } -.bi-6-square::before { content: "\f7b7"; } -.bi-7-circle-1::before { content: "\f7b8"; } -.bi-7-circle-fill-1::before { content: "\f7b9"; } -.bi-7-circle-fill::before { content: "\f7ba"; } -.bi-7-circle::before { content: "\f7bb"; } -.bi-7-square-fill::before { content: "\f7bc"; } -.bi-7-square::before { content: "\f7bd"; } -.bi-8-circle-1::before { content: "\f7be"; } -.bi-8-circle-fill-1::before { content: "\f7bf"; } -.bi-8-circle-fill::before { content: "\f7c0"; } -.bi-8-circle::before { content: "\f7c1"; } -.bi-8-square-fill::before { content: "\f7c2"; } -.bi-8-square::before { content: "\f7c3"; } -.bi-9-circle-1::before { content: "\f7c4"; } -.bi-9-circle-fill-1::before { content: "\f7c5"; } -.bi-9-circle-fill::before { content: "\f7c6"; } -.bi-9-circle::before { content: "\f7c7"; } -.bi-9-square-fill::before { content: "\f7c8"; } -.bi-9-square::before { content: "\f7c9"; } -.bi-airplane-engines-fill::before { content: "\f7ca"; } -.bi-airplane-engines::before { content: "\f7cb"; } -.bi-airplane-fill::before { content: "\f7cc"; } -.bi-airplane::before { content: "\f7cd"; } -.bi-alexa::before { content: "\f7ce"; } -.bi-alipay::before { content: "\f7cf"; } -.bi-android::before { content: "\f7d0"; } -.bi-android2::before { content: "\f7d1"; } -.bi-box-fill::before { content: "\f7d2"; } -.bi-box-seam-fill::before { content: "\f7d3"; } -.bi-browser-chrome::before { content: "\f7d4"; } -.bi-browser-edge::before { content: "\f7d5"; } -.bi-browser-firefox::before { content: "\f7d6"; } -.bi-browser-safari::before { content: "\f7d7"; } -.bi-c-circle-1::before { content: "\f7d8"; } -.bi-c-circle-fill-1::before { content: "\f7d9"; } -.bi-c-circle-fill::before { content: "\f7da"; } -.bi-c-circle::before { content: "\f7db"; } -.bi-c-square-fill::before { content: "\f7dc"; } -.bi-c-square::before { content: "\f7dd"; } -.bi-capsule-pill::before { content: "\f7de"; } -.bi-capsule::before { content: "\f7df"; } -.bi-car-front-fill::before { content: "\f7e0"; } -.bi-car-front::before { content: "\f7e1"; } -.bi-cassette-fill::before { content: "\f7e2"; } -.bi-cassette::before { content: "\f7e3"; } -.bi-cc-circle-1::before { content: "\f7e4"; } -.bi-cc-circle-fill-1::before { content: "\f7e5"; } -.bi-cc-circle-fill::before { content: "\f7e6"; } -.bi-cc-circle::before { content: "\f7e7"; } -.bi-cc-square-fill::before { content: "\f7e8"; } -.bi-cc-square::before { content: "\f7e9"; } -.bi-cup-hot-fill::before { content: "\f7ea"; } -.bi-cup-hot::before { content: "\f7eb"; } -.bi-currency-rupee::before { content: "\f7ec"; } -.bi-dropbox::before { content: "\f7ed"; } -.bi-escape::before { content: "\f7ee"; } -.bi-fast-forward-btn-fill::before { content: "\f7ef"; } -.bi-fast-forward-btn::before { content: "\f7f0"; } -.bi-fast-forward-circle-fill::before { content: "\f7f1"; } -.bi-fast-forward-circle::before { content: "\f7f2"; } -.bi-fast-forward-fill::before { content: "\f7f3"; } -.bi-fast-forward::before { content: "\f7f4"; } -.bi-filetype-sql::before { content: "\f7f5"; } -.bi-fire::before { content: "\f7f6"; } -.bi-google-play::before { content: "\f7f7"; } -.bi-h-circle-1::before { content: "\f7f8"; } -.bi-h-circle-fill-1::before { content: "\f7f9"; } -.bi-h-circle-fill::before { content: "\f7fa"; } -.bi-h-circle::before { content: "\f7fb"; } -.bi-h-square-fill::before { content: "\f7fc"; } -.bi-h-square::before { content: "\f7fd"; } -.bi-indent::before { content: "\f7fe"; } -.bi-lungs-fill::before { content: "\f7ff"; } -.bi-lungs::before { content: "\f800"; } -.bi-microsoft-teams::before { content: "\f801"; } -.bi-p-circle-1::before { content: "\f802"; } -.bi-p-circle-fill-1::before { content: "\f803"; } -.bi-p-circle-fill::before { content: "\f804"; } -.bi-p-circle::before { content: "\f805"; } -.bi-p-square-fill::before { content: "\f806"; } -.bi-p-square::before { content: "\f807"; } -.bi-pass-fill::before { content: "\f808"; } -.bi-pass::before { content: "\f809"; } -.bi-prescription::before { content: "\f80a"; } -.bi-prescription2::before { content: "\f80b"; } -.bi-r-circle-1::before { content: "\f80c"; } -.bi-r-circle-fill-1::before { content: "\f80d"; } -.bi-r-circle-fill::before { content: "\f80e"; } -.bi-r-circle::before { content: "\f80f"; } -.bi-r-square-fill::before { content: "\f810"; } -.bi-r-square::before { content: "\f811"; } -.bi-repeat-1::before { content: "\f812"; } -.bi-repeat::before { content: "\f813"; } -.bi-rewind-btn-fill::before { content: "\f814"; } -.bi-rewind-btn::before { content: "\f815"; } -.bi-rewind-circle-fill::before { content: "\f816"; } -.bi-rewind-circle::before { content: "\f817"; } -.bi-rewind-fill::before { content: "\f818"; } -.bi-rewind::before { content: "\f819"; } -.bi-train-freight-front-fill::before { content: "\f81a"; } -.bi-train-freight-front::before { content: "\f81b"; } -.bi-train-front-fill::before { content: "\f81c"; } -.bi-train-front::before { content: "\f81d"; } -.bi-train-lightrail-front-fill::before { content: "\f81e"; } -.bi-train-lightrail-front::before { content: "\f81f"; } -.bi-truck-front-fill::before { content: "\f820"; } -.bi-truck-front::before { content: "\f821"; } -.bi-ubuntu::before { content: "\f822"; } -.bi-unindent::before { content: "\f823"; } -.bi-unity::before { content: "\f824"; } -.bi-universal-access-circle::before { content: "\f825"; } -.bi-universal-access::before { content: "\f826"; } -.bi-virus::before { content: "\f827"; } -.bi-virus2::before { content: "\f828"; } -.bi-wechat::before { content: "\f829"; } -.bi-yelp::before { content: "\f82a"; } -.bi-sign-stop-fill::before { content: "\f82b"; } -.bi-sign-stop-lights-fill::before { content: "\f82c"; } -.bi-sign-stop-lights::before { content: "\f82d"; } -.bi-sign-stop::before { content: "\f82e"; } -.bi-sign-turn-left-fill::before { content: "\f82f"; } -.bi-sign-turn-left::before { content: "\f830"; } -.bi-sign-turn-right-fill::before { content: "\f831"; } -.bi-sign-turn-right::before { content: "\f832"; } -.bi-sign-turn-slight-left-fill::before { content: "\f833"; } -.bi-sign-turn-slight-left::before { content: "\f834"; } -.bi-sign-turn-slight-right-fill::before { content: "\f835"; } -.bi-sign-turn-slight-right::before { content: "\f836"; } -.bi-sign-yield-fill::before { content: "\f837"; } -.bi-sign-yield::before { content: "\f838"; } -.bi-ev-station-fill::before { content: "\f839"; } -.bi-ev-station::before { content: "\f83a"; } -.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } -.bi-fuel-pump-diesel::before { content: "\f83c"; } -.bi-fuel-pump-fill::before { content: "\f83d"; } -.bi-fuel-pump::before { content: "\f83e"; } -.bi-0-circle-fill::before { content: "\f83f"; } -.bi-0-circle::before { content: "\f840"; } -.bi-0-square-fill::before { content: "\f841"; } -.bi-0-square::before { content: "\f842"; } -.bi-rocket-fill::before { content: "\f843"; } -.bi-rocket-takeoff-fill::before { content: "\f844"; } -.bi-rocket-takeoff::before { content: "\f845"; } -.bi-rocket::before { content: "\f846"; } -.bi-stripe::before { content: "\f847"; } -.bi-subscript::before { content: "\f848"; } -.bi-superscript::before { content: "\f849"; } -.bi-trello::before { content: "\f84a"; } -.bi-envelope-at-fill::before { content: "\f84b"; } -.bi-envelope-at::before { content: "\f84c"; } -.bi-regex::before { content: "\f84d"; } -.bi-text-wrap::before { content: "\f84e"; } -.bi-sign-dead-end-fill::before { content: "\f84f"; } -.bi-sign-dead-end::before { content: "\f850"; } -.bi-sign-do-not-enter-fill::before { content: "\f851"; } -.bi-sign-do-not-enter::before { content: "\f852"; } -.bi-sign-intersection-fill::before { content: "\f853"; } -.bi-sign-intersection-side-fill::before { content: "\f854"; } -.bi-sign-intersection-side::before { content: "\f855"; } -.bi-sign-intersection-t-fill::before { content: "\f856"; } -.bi-sign-intersection-t::before { content: "\f857"; } -.bi-sign-intersection-y-fill::before { content: "\f858"; } -.bi-sign-intersection-y::before { content: "\f859"; } -.bi-sign-intersection::before { content: "\f85a"; } -.bi-sign-merge-left-fill::before { content: "\f85b"; } -.bi-sign-merge-left::before { content: "\f85c"; } -.bi-sign-merge-right-fill::before { content: "\f85d"; } -.bi-sign-merge-right::before { content: "\f85e"; } -.bi-sign-no-left-turn-fill::before { content: "\f85f"; } -.bi-sign-no-left-turn::before { content: "\f860"; } -.bi-sign-no-parking-fill::before { content: "\f861"; } -.bi-sign-no-parking::before { content: "\f862"; } -.bi-sign-no-right-turn-fill::before { content: "\f863"; } -.bi-sign-no-right-turn::before { content: "\f864"; } -.bi-sign-railroad-fill::before { content: "\f865"; } -.bi-sign-railroad::before { content: "\f866"; } -.bi-building-add::before { content: "\f867"; } -.bi-building-check::before { content: "\f868"; } -.bi-building-dash::before { content: "\f869"; } -.bi-building-down::before { content: "\f86a"; } -.bi-building-exclamation::before { content: "\f86b"; } -.bi-building-fill-add::before { content: "\f86c"; } -.bi-building-fill-check::before { content: "\f86d"; } -.bi-building-fill-dash::before { content: "\f86e"; } -.bi-building-fill-down::before { content: "\f86f"; } -.bi-building-fill-exclamation::before { content: "\f870"; } -.bi-building-fill-gear::before { content: "\f871"; } -.bi-building-fill-lock::before { content: "\f872"; } -.bi-building-fill-slash::before { content: "\f873"; } -.bi-building-fill-up::before { content: "\f874"; } -.bi-building-fill-x::before { content: "\f875"; } -.bi-building-fill::before { content: "\f876"; } -.bi-building-gear::before { content: "\f877"; } -.bi-building-lock::before { content: "\f878"; } -.bi-building-slash::before { content: "\f879"; } -.bi-building-up::before { content: "\f87a"; } -.bi-building-x::before { content: "\f87b"; } -.bi-buildings-fill::before { content: "\f87c"; } -.bi-buildings::before { content: "\f87d"; } -.bi-bus-front-fill::before { content: "\f87e"; } -.bi-bus-front::before { content: "\f87f"; } -.bi-ev-front-fill::before { content: "\f880"; } -.bi-ev-front::before { content: "\f881"; } -.bi-globe-americas::before { content: "\f882"; } -.bi-globe-asia-australia::before { content: "\f883"; } -.bi-globe-central-south-asia::before { content: "\f884"; } -.bi-globe-europe-africa::before { content: "\f885"; } -.bi-house-add-fill::before { content: "\f886"; } -.bi-house-add::before { content: "\f887"; } -.bi-house-check-fill::before { content: "\f888"; } -.bi-house-check::before { content: "\f889"; } -.bi-house-dash-fill::before { content: "\f88a"; } -.bi-house-dash::before { content: "\f88b"; } -.bi-house-down-fill::before { content: "\f88c"; } -.bi-house-down::before { content: "\f88d"; } -.bi-house-exclamation-fill::before { content: "\f88e"; } -.bi-house-exclamation::before { content: "\f88f"; } -.bi-house-gear-fill::before { content: "\f890"; } -.bi-house-gear::before { content: "\f891"; } -.bi-house-lock-fill::before { content: "\f892"; } -.bi-house-lock::before { content: "\f893"; } -.bi-house-slash-fill::before { content: "\f894"; } -.bi-house-slash::before { content: "\f895"; } -.bi-house-up-fill::before { content: "\f896"; } -.bi-house-up::before { content: "\f897"; } -.bi-house-x-fill::before { content: "\f898"; } -.bi-house-x::before { content: "\f899"; } -.bi-person-add::before { content: "\f89a"; } -.bi-person-down::before { content: "\f89b"; } -.bi-person-exclamation::before { content: "\f89c"; } -.bi-person-fill-add::before { content: "\f89d"; } -.bi-person-fill-check::before { content: "\f89e"; } -.bi-person-fill-dash::before { content: "\f89f"; } -.bi-person-fill-down::before { content: "\f8a0"; } -.bi-person-fill-exclamation::before { content: "\f8a1"; } -.bi-person-fill-gear::before { content: "\f8a2"; } -.bi-person-fill-lock::before { content: "\f8a3"; } -.bi-person-fill-slash::before { content: "\f8a4"; } -.bi-person-fill-up::before { content: "\f8a5"; } -.bi-person-fill-x::before { content: "\f8a6"; } -.bi-person-gear::before { content: "\f8a7"; } -.bi-person-lock::before { content: "\f8a8"; } -.bi-person-slash::before { content: "\f8a9"; } -.bi-person-up::before { content: "\f8aa"; } -.bi-scooter::before { content: "\f8ab"; } -.bi-taxi-front-fill::before { content: "\f8ac"; } -.bi-taxi-front::before { content: "\f8ad"; } -.bi-amd::before { content: "\f8ae"; } -.bi-database-add::before { content: "\f8af"; } -.bi-database-check::before { content: "\f8b0"; } -.bi-database-dash::before { content: "\f8b1"; } -.bi-database-down::before { content: "\f8b2"; } -.bi-database-exclamation::before { content: "\f8b3"; } -.bi-database-fill-add::before { content: "\f8b4"; } -.bi-database-fill-check::before { content: "\f8b5"; } -.bi-database-fill-dash::before { content: "\f8b6"; } -.bi-database-fill-down::before { content: "\f8b7"; } -.bi-database-fill-exclamation::before { content: "\f8b8"; } -.bi-database-fill-gear::before { content: "\f8b9"; } -.bi-database-fill-lock::before { content: "\f8ba"; } -.bi-database-fill-slash::before { content: "\f8bb"; } -.bi-database-fill-up::before { content: "\f8bc"; } -.bi-database-fill-x::before { content: "\f8bd"; } -.bi-database-fill::before { content: "\f8be"; } -.bi-database-gear::before { content: "\f8bf"; } -.bi-database-lock::before { content: "\f8c0"; } -.bi-database-slash::before { content: "\f8c1"; } -.bi-database-up::before { content: "\f8c2"; } -.bi-database-x::before { content: "\f8c3"; } -.bi-database::before { content: "\f8c4"; } -.bi-houses-fill::before { content: "\f8c5"; } -.bi-houses::before { content: "\f8c6"; } -.bi-nvidia::before { content: "\f8c7"; } -.bi-person-vcard-fill::before { content: "\f8c8"; } -.bi-person-vcard::before { content: "\f8c9"; } -.bi-sina-weibo::before { content: "\f8ca"; } -.bi-tencent-qq::before { content: "\f8cb"; } -.bi-wikipedia::before { content: "\f8cc"; } diff --git a/choosing_files/libs/bootstrap/bootstrap-icons.woff b/choosing_files/libs/bootstrap/bootstrap-icons.woff deleted file mode 100644 index 18d21d4..0000000 Binary files a/choosing_files/libs/bootstrap/bootstrap-icons.woff and /dev/null differ diff --git a/choosing_files/libs/bootstrap/bootstrap.min.css b/choosing_files/libs/bootstrap/bootstrap.min.css deleted file mode 100644 index 0c07a5b..0000000 --- a/choosing_files/libs/bootstrap/bootstrap.min.css +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * Bootstrap v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #ffffff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-default: #dee2e6;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-default-rgb: 222, 226, 230;--bs-primary-rgb: 13, 110, 253;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 220, 53, 69;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 33, 37, 41;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 17px;--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-bg: #ffffff}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#0d6efd;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f6f6f6;padding:.5rem;border:1px solid #dee2e6;border-radius:.25rem}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f6f6f6;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: #212529;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #212529;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #212529;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid #9ba5ae}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #cfe2ff;--bs-table-striped-bg: #c5d7f2;--bs-table-striped-color: #000;--bs-table-active-bg: #bacbe6;--bs-table-active-color: #000;--bs-table-hover-bg: #bfd1ec;--bs-table-hover-color: #000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #d1e7dd;--bs-table-striped-bg: #c7dbd2;--bs-table-striped-color: #000;--bs-table-active-bg: #bcd0c7;--bs-table-active-color: #000;--bs-table-hover-bg: #c1d6cc;--bs-table-hover-color: #000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg: #cff4fc;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg: #fff3cd;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg: #f8d7da;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #212529;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #ffffff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #ffffff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #ffffff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#198754;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#198754}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#dc3545}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-default:hover{color:#000;background-color:#e3e6ea;border-color:#e1e5e9}.btn-check:focus+.btn-default,.btn-default:focus{color:#000;background-color:#e3e6ea;border-color:#e1e5e9;box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#000;background-color:#e5e8eb;border-color:#e1e5e9}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-default:disabled,.btn-default.disabled{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info:disabled,.btn-info.disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-default{color:#dee2e6;border-color:#dee2e6;background-color:rgba(0,0,0,0)}.btn-outline-default:hover{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#dee2e6;background-color:rgba(0,0,0,0)}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#0d6efd;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#198754;border-color:#198754;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#198754;border-color:#198754}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#198754;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#0dcaf0;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#212529;border-color:#212529;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#212529;border-color:#212529}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#212529;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25 0;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#0d6efd}.navbar-light .navbar-brand{color:#fdfeff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fdfeff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,0)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fdfeff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#0d6efd}.navbar-dark .navbar-brand{color:#fdfeff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fdfeff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,0)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fdfeff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#595a5c;background-color:#f8f9fa;border-color:#f5f6f8}.alert-default .alert-link{color:#47484a}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;display:-webkit-flex;height:1rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#595a5c;background-color:#f8f9fa}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#595a5c;background-color:#dfe0e1}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#595a5c;border-color:#595a5c}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:rgba(0,0,0,0) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#dee2e6}.link-default:hover,.link-default:focus{color:#e5e8eb}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#dee2e6 !important}.border-primary{border-color:#0d6efd !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffc107 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2rem !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#000}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#000}.bg-warning{color:#000}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.25rem;color:#212529;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#dee2e6;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#dee2e6;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#dee2e6;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#dee2e6}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#212529}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! -* -* ansi colors from IPython notebook's -* -*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #ffffff;--quarto-body-color: #212529;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:absolute;right:.5em;left:inherit;background-color:rgba(0,0,0,0)}:root{--mermaid-bg-color: #ffffff;--mermaid-edge-color: #6c757d;--mermaid-node-fg-color: #212529;--mermaid-fg-color: #212529;--mermaid-fg-color--lighter: #383f45;--mermaid-fg-color--lightest: #4e5862;--mermaid-font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, Liberation Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;--mermaid-label-bg-color: #ffffff;--mermaid-label-fg-color: #0d6efd;--mermaid-node-bg-color: rgba(13, 110, 253, 0.1);--mermaid-node-fg-color: #212529}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1250px - 3em )) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{opacity:.9;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#5a6570}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:.9rem}.panel-caption,.figure-caption,figcaption{color:#5a6570}.table-caption,caption{color:#212529}.quarto-layout-cell[data-ref-parent] caption{color:#5a6570}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#5a6570;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#5a6570}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f6f6f6;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#0d6efd}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.sidebar .quarto-alternate-formats a,.sidebar .quarto-alternate-notebooks a{text-decoration:none}.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#0d6efd}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2{margin-top:1rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul,.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #0d6efd;color:#0d6efd !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#0d6efd !important}kbd,.kbd{color:#212529;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>thead{border-top-width:1px;border-top-color:#dee2e6;border-bottom:1px solid #9ba5ae}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-titled .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#0d6efd}div.callout-note.callout-style-default>.callout-header{background-color:#e7f1ff}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#198754}div.callout-tip.callout-style-default>.callout-header{background-color:#e8f3ee}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ffc107}div.callout-warning.callout-style-default>.callout-header{background-color:#fff9e6}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#fd7e14}div.callout-caution.callout-style-default>.callout-header{background-color:#fff2e8}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#dc3545}div.callout-important.callout-style-default>.callout-header{background-color:#fcebec}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#212529}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#fefefe;background-color:#6c757d;border-color:#6c757d}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#fefefe;background-color:#828a91;border-color:#7b838a}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#fefefe;background-color:#828a91;border-color:#7b838a;box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#000;background-color:#899197;border-color:#7b838a}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#0d6efd;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner,body.nav-sidebar .quarto-title-banner{display:none}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:rgba(233,236,239,.65)}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:var(--bs-font-monospace);color:#383f45;border:solid #383f45 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:var(--bs-font-monospace);color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table>thead{border-top-width:0}.table>:not(caption)>*:not(:last-child)>*{border-bottom-color:#d3d8dc;border-bottom-style:solid;border-bottom-width:1px}.table>:not(:first-child){border-top:1px solid #9ba5ae;border-bottom:1px solid inherit}.table tbody{border-bottom-color:#9ba5ae}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#0d6efd}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#212529}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}.quarto-title-tools-only{display:flex;justify-content:right}/*# sourceMappingURL=397ef2e52d54cf686e4908b90039e9db.css.map */ diff --git a/choosing_files/libs/bootstrap/bootstrap.min.js b/choosing_files/libs/bootstrap/bootstrap.min.js deleted file mode 100644 index cc0a255..0000000 --- a/choosing_files/libs/bootstrap/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/choosing_files/libs/clipboard/clipboard.min.js b/choosing_files/libs/clipboard/clipboard.min.js deleted file mode 100644 index 1103f81..0000000 --- a/choosing_files/libs/clipboard/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.11 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1>> 0; - - // 4. If isCallable(callback) is false, throw a TypeError exception. - // See: http://es5.github.com/#x9.11 - if (typeof callback !== "function") { - throw new TypeError(callback + ' is not a function'); - } - - // 5. If thisArg was supplied, let T be thisArg; else let - // T be undefined. - if (arguments.length > 1) { - T = thisArg; - } - - // 6. Let k be 0 - k = 0; - - // 7. Repeat, while k < len - while (k < len) { - - var kValue; - - // a. Let Pk be ToString(k). - // This is implicit for LHS operands of the in operator - // b. Let kPresent be the result of calling the HasProperty - // internal method of O with argument Pk. - // This step can be combined with c - // c. If kPresent is true, then - if (k in O) { - - // i. Let kValue be the result of calling the Get internal - // method of O with argument Pk. - kValue = O[k]; - - // ii. Call the Call internal method of callback with T as - // the this value and argument list containing kValue, k, and O. - callback.call(T, kValue, k, O); - } - // d. Increase k by 1. - k++; - } - // 8. return undefined - }; -} - -// Production steps of ECMA-262, Edition 5, 15.4.4.19 -// Reference: http://es5.github.io/#x15.4.4.19 -if (!Array.prototype.map) { - - Array.prototype.map = function(callback, thisArg) { - - var T, A, k; - - if (this == null) { - throw new TypeError(' this is null or not defined'); - } - - // 1. Let O be the result of calling ToObject passing the |this| - // value as the argument. - var O = Object(this); - - // 2. Let lenValue be the result of calling the Get internal - // method of O with the argument "length". - // 3. Let len be ToUint32(lenValue). - var len = O.length >>> 0; - - // 4. If IsCallable(callback) is false, throw a TypeError exception. - // See: http://es5.github.com/#x9.11 - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - - // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. - if (arguments.length > 1) { - T = thisArg; - } - - // 6. Let A be a new array created as if by the expression new Array(len) - // where Array is the standard built-in constructor with that name and - // len is the value of len. - A = new Array(len); - - // 7. Let k be 0 - k = 0; - - // 8. Repeat, while k < len - while (k < len) { - - var kValue, mappedValue; - - // a. Let Pk be ToString(k). - // This is implicit for LHS operands of the in operator - // b. Let kPresent be the result of calling the HasProperty internal - // method of O with argument Pk. - // This step can be combined with c - // c. If kPresent is true, then - if (k in O) { - - // i. Let kValue be the result of calling the Get internal - // method of O with argument Pk. - kValue = O[k]; - - // ii. Let mappedValue be the result of calling the Call internal - // method of callback with T as the this value and argument - // list containing kValue, k, and O. - mappedValue = callback.call(T, kValue, k, O); - - // iii. Call the DefineOwnProperty internal method of A with arguments - // Pk, Property Descriptor - // { Value: mappedValue, - // Writable: true, - // Enumerable: true, - // Configurable: true }, - // and false. - - // In browsers that support Object.defineProperty, use the following: - // Object.defineProperty(A, k, { - // value: mappedValue, - // writable: true, - // enumerable: true, - // configurable: true - // }); - - // For best browser support, use the following: - A[k] = mappedValue; - } - // d. Increase k by 1. - k++; - } - - // 9. return A - return A; - }; -} - -var PagedTable = function (pagedTable) { - var me = this; - - var source = function(pagedTable) { - var sourceElems = [].slice.call(pagedTable.children).filter(function(e) { - return e.hasAttribute("data-pagedtable-source"); - }); - - if (sourceElems === null || sourceElems.length !== 1) { - throw("A single data-pagedtable-source was not found"); - } - - return JSON.parse(sourceElems[0].innerHTML); - }(pagedTable); - - var options = function(source) { - var options = typeof(source.options) !== "undefined" && - source.options !== null ? source.options : {}; - - var columns = typeof(options.columns) !== "undefined" ? options.columns : {}; - var rows = typeof(options.rows) !== "undefined" ? options.rows : {}; - - var positiveIntOrNull = function(value) { - return parseInt(value) >= 0 ? parseInt(value) : null; - }; - - return { - pages: positiveIntOrNull(options.pages), - rows: { - min: positiveIntOrNull(rows.min), - max: positiveIntOrNull(rows.max), - total: positiveIntOrNull(rows.total) - }, - columns: { - min: positiveIntOrNull(columns.min), - max: positiveIntOrNull(columns.max), - total: positiveIntOrNull(columns.total) - } - }; - }(source); - - var Measurer = function() { - - // set some default initial values that will get adjusted in runtime - me.measures = { - padding: 12, - character: 8, - height: 15, - defaults: true - }; - - me.calculate = function(measuresCell) { - if (!me.measures.defaults) - return; - - var measuresCellStyle = window.getComputedStyle(measuresCell, null); - - var newPadding = parsePadding(measuresCellStyle.paddingLeft) + - parsePadding(measuresCellStyle.paddingRight); - - var sampleString = "ABCDEFGHIJ0123456789"; - var newCharacter = Math.ceil(measuresCell.clientWidth / sampleString.length); - - if (newPadding <= 0 || newCharacter <= 0) - return; - - me.measures.padding = newPadding; - me.measures.character = newCharacter; - me.measures.height = measuresCell.clientHeight; - me.measures.defaults = false; - }; - - return me; - }; - - var Page = function(data, options) { - var me = this; - - var defaults = { - max: 7, - rows: 10 - }; - - var totalPages = function() { - return Math.ceil(data.length / me.rows); - }; - - me.number = 0; - me.max = options.pages !== null ? options.pages : defaults.max; - me.visible = me.max; - me.rows = options.rows.min !== null ? options.rows.min : defaults.rows; - me.total = totalPages(); - - me.setRows = function(newRows) { - me.rows = newRows; - me.total = totalPages(); - }; - - me.setPageNumber = function(newPageNumber) { - if (newPageNumber < 0) newPageNumber = 0; - if (newPageNumber >= me.total) newPageNumber = me.total - 1; - - me.number = newPageNumber; - }; - - me.setVisiblePages = function(visiblePages) { - me.visible = Math.min(me.max, visiblePages); - me.setPageNumber(me.number); - }; - - me.getVisiblePageRange = function() { - var start = me.number - Math.max(Math.floor((me.visible - 1) / 2), 0); - var end = me.number + Math.floor(me.visible / 2) + 1; - var pageCount = me.total; - - if (start < 0) { - var diffToStart = 0 - start; - start += diffToStart; - end += diffToStart; - } - - if (end > pageCount) { - var diffToEnd = end - pageCount; - start -= diffToEnd; - end -= diffToEnd; - } - - start = start < 0 ? 0 : start; - end = end >= pageCount ? pageCount : end; - - var first = false; - var last = false; - - if (start > 0 && me.visible > 1) { - start = start + 1; - first = true; - } - - if (end < pageCount && me.visible > 2) { - end = end - 1; - last = true; - } - - return { - first: first, - start: start, - end: end, - last: last - }; - }; - - me.getRowStart = function() { - var rowStart = page.number * page.rows; - if (rowStart < 0) - rowStart = 0; - - return rowStart; - }; - - me.getRowEnd = function() { - var rowStart = me.getRowStart(); - return Math.min(rowStart + me.rows, data.length); - }; - - me.getPaddingRows = function() { - var rowStart = me.getRowStart(); - var rowEnd = me.getRowEnd(); - return data.length > me.rows ? me.rows - (rowEnd - rowStart) : 0; - }; - }; - - var Columns = function(data, columns, options) { - var me = this; - - me.defaults = { - min: 5 - }; - - me.number = 0; - me.visible = 0; - me.total = columns.length; - me.subset = []; - me.padding = 0; - me.min = options.columns.min !== null ? options.columns.min : me.defaults.min; - me.max = options.columns.max !== null ? options.columns.max : null; - me.widths = {}; - - var widthsLookAhead = Math.max(100, options.rows.min); - var paddingColChars = 10; - - me.emptyNames = function() { - columns.forEach(function(column) { - if (columns.label !== null && columns.label !== "") - return false; - }); - - return true; - }; - - var parsePadding = function(value) { - return parseInt(value) >= 0 ? parseInt(value) : 0; - }; - - me.calculateWidths = function(measures) { - columns.forEach(function(column) { - var maxChars = Math.max( - column.label.toString().length, - column.type.toString().length - ); - - for (var idxRow = 0; idxRow < Math.min(widthsLookAhead, data.length); idxRow++) { - maxChars = Math.max(maxChars, data[idxRow][column.name.toString()].length); - } - - me.widths[column.name] = { - // width in characters - chars: maxChars, - // width for the inner html columns - inner: maxChars * measures.character, - // width adding outer styles like padding - outer: maxChars * measures.character + measures.padding - }; - }); - }; - - me.getWidth = function() { - var widthOuter = 0; - for (var idxCol = 0; idxCol < me.subset.length; idxCol++) { - var columnName = me.subset[idxCol].name; - widthOuter = widthOuter + me.widths[columnName].outer; - } - - widthOuter = widthOuter + me.padding * paddingColChars * measurer.measures.character; - - if (me.hasMoreLeftColumns()) { - widthOuter = widthOuter + columnNavigationWidthPX + measurer.measures.padding; - } - - if (me.hasMoreRightColumns()) { - widthOuter = widthOuter + columnNavigationWidthPX + measurer.measures.padding; - } - - return widthOuter; - }; - - me.updateSlice = function() { - if (me.number + me.visible >= me.total) - me.number = me.total - me.visible; - - if (me.number < 0) me.number = 0; - - me.subset = columns.slice(me.number, Math.min(me.number + me.visible, me.total)); - - me.subset = me.subset.map(function(column) { - Object.keys(column).forEach(function(colKey) { - column[colKey] = column[colKey] === null ? "" : column[colKey].toString(); - }); - - column.width = null; - return column; - }); - }; - - me.setVisibleColumns = function(columnNumber, newVisibleColumns, paddingCount) { - me.number = columnNumber; - me.visible = newVisibleColumns; - me.padding = paddingCount; - - me.updateSlice(); - }; - - me.incColumnNumber = function(increment) { - me.number = me.number + increment; - }; - - me.setColumnNumber = function(newNumber) { - me.number = newNumber; - }; - - me.setPaddingCount = function(newPadding) { - me.padding = newPadding; - }; - - me.getPaddingCount = function() { - return me.padding; - }; - - me.hasMoreLeftColumns = function() { - return me.number > 0; - }; - - me.hasMoreRightColumns = function() { - return me.number + me.visible < me.total; - }; - - me.updateSlice(0); - return me; - }; - - var data = source.data; - var page = new Page(data, options); - var measurer = new Measurer(data, options); - var columns = new Columns(data, source.columns, options); - - var table = null; - var tableDiv = null; - var header = null; - var footer = null; - var tbody = null; - - // Caches pagedTable.clientWidth, specially for webkit - var cachedPagedTableClientWidth = null; - - var onChangeCallbacks = []; - - var clearSelection = function() { - if(document.selection && document.selection.empty) { - document.selection.empty(); - } else if(window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - } - }; - - var columnNavigationWidthPX = 5; - - var renderColumnNavigation = function(increment, backwards) { - var arrow = document.createElement("div"); - arrow.setAttribute("style", - "border-top: " + columnNavigationWidthPX + "px solid transparent;" + - "border-bottom: " + columnNavigationWidthPX + "px solid transparent;" + - "border-" + (backwards ? "right" : "left") + ": " + columnNavigationWidthPX + "px solid;"); - - var header = document.createElement("th"); - header.appendChild(arrow); - header.setAttribute("style", - "cursor: pointer;" + - "vertical-align: middle;" + - "min-width: " + columnNavigationWidthPX + "px;" + - "width: " + columnNavigationWidthPX + "px;"); - - header.onclick = function() { - columns.incColumnNumber(backwards ? -1 : increment); - - me.animateColumns(backwards); - renderFooter(); - - clearSelection(); - triggerOnChange(); - }; - - return header; - }; - - var maxColumnWidth = function(width) { - var padding = 80; - var columnMax = Math.max(cachedPagedTableClientWidth - padding, 0); - - return parseInt(width) > 0 ? - Math.min(columnMax, parseInt(width)) + "px" : - columnMax + "px"; - }; - - var clearHeader = function() { - var thead = pagedTable.querySelectorAll("thead")[0]; - thead.innerHTML = ""; - }; - - var renderHeader = function(clear) { - cachedPagedTableClientWidth = pagedTable.clientWidth; - - var fragment = document.createDocumentFragment(); - - header = document.createElement("tr"); - fragment.appendChild(header); - - if (columns.number > 0) - header.appendChild(renderColumnNavigation(-columns.visible, true)); - - columns.subset = columns.subset.map(function(columnData) { - var column = document.createElement("th"); - column.setAttribute("align", columnData.align); - column.style.textAlign = columnData.align; - - column.style.maxWidth = maxColumnWidth(null); - if (columnData.width) { - column.style.minWidth = - column.style.maxWidth = maxColumnWidth(columnData.width); - } - - var columnName = document.createElement("div"); - columnName.setAttribute("class", "pagedtable-header-name"); - if (columnData.label === "") { - columnName.innerHTML = " "; - } - else { - columnName.appendChild(document.createTextNode(columnData.label)); - } - column.appendChild(columnName); - - var columnType = document.createElement("div"); - columnType.setAttribute("class", "pagedtable-header-type"); - if (columnData.type === "") { - columnType.innerHTML = " "; - } - else { - columnType.appendChild(document.createTextNode("<" + columnData.type + ">")); - } - column.appendChild(columnType); - - header.appendChild(column); - - columnData.element = column; - - return columnData; - }); - - for (var idx = 0; idx < columns.getPaddingCount(); idx++) { - var paddingCol = document.createElement("th"); - paddingCol.setAttribute("class", "pagedtable-padding-col"); - header.appendChild(paddingCol); - } - - if (columns.number + columns.visible < columns.total) - header.appendChild(renderColumnNavigation(columns.visible, false)); - - if (typeof(clear) == "undefined" || clear) clearHeader(); - var thead = pagedTable.querySelectorAll("thead")[0]; - thead.appendChild(fragment); - }; - - me.animateColumns = function(backwards) { - var thead = pagedTable.querySelectorAll("thead")[0]; - - var headerOld = thead.querySelectorAll("tr")[0]; - var tbodyOld = table.querySelectorAll("tbody")[0]; - - me.fitColumns(backwards); - - renderHeader(false); - - header.style.opacity = "0"; - header.style.transform = backwards ? "translateX(-30px)" : "translateX(30px)"; - header.style.transition = "transform 200ms linear, opacity 200ms"; - header.style.transitionDelay = "0"; - - renderBody(false); - - if (headerOld) { - headerOld.style.position = "absolute"; - headerOld.style.transform = "translateX(0px)"; - headerOld.style.opacity = "1"; - headerOld.style.transition = "transform 100ms linear, opacity 100ms"; - headerOld.setAttribute("class", "pagedtable-remove-head"); - if (headerOld.style.transitionEnd) { - headerOld.addEventListener("transitionend", function() { - var headerOldByClass = thead.querySelector(".pagedtable-remove-head"); - if (headerOldByClass) thead.removeChild(headerOldByClass); - }); - } - else { - thead.removeChild(headerOld); - } - } - - if (tbodyOld) table.removeChild(tbodyOld); - - tbody.style.opacity = "0"; - tbody.style.transition = "transform 200ms linear, opacity 200ms"; - tbody.style.transitionDelay = "0ms"; - - // force relayout - window.getComputedStyle(header).opacity; - window.getComputedStyle(tbody).opacity; - - if (headerOld) { - headerOld.style.transform = backwards ? "translateX(20px)" : "translateX(-30px)"; - headerOld.style.opacity = "0"; - } - - header.style.transform = "translateX(0px)"; - header.style.opacity = "1"; - - tbody.style.opacity = "1"; - } - - me.onChange = function(callback) { - onChangeCallbacks.push(callback); - }; - - var triggerOnChange = function() { - onChangeCallbacks.forEach(function(onChange) { - onChange(); - }); - }; - - var clearBody = function() { - if (tbody) { - table.removeChild(tbody); - tbody = null; - } - }; - - var renderBody = function(clear) { - cachedPagedTableClientWidth = pagedTable.clientWidth - - var fragment = document.createDocumentFragment(); - - var pageData = data.slice(page.getRowStart(), page.getRowEnd()); - - pageData.forEach(function(dataRow, idxRow) { - var htmlRow = document.createElement("tr"); - htmlRow.setAttribute("class", (idxRow % 2 !==0) ? "even" : "odd"); - - if (columns.hasMoreLeftColumns()) - htmlRow.appendChild(document.createElement("td")); - - columns.subset.forEach(function(columnData) { - var cellName = columnData.name; - var dataCell = dataRow[cellName]; - var htmlCell = document.createElement("td"); - - if (dataCell === "NA") htmlCell.setAttribute("class", "pagedtable-na-cell"); - if (dataCell === "__NA__") dataCell = "NA"; - - var cellText = document.createTextNode(dataCell); - htmlCell.appendChild(cellText); - if (dataCell.length > 50) { - htmlCell.setAttribute("title", dataCell); - } - htmlCell.setAttribute("align", columnData.align); - htmlCell.style.textAlign = columnData.align; - htmlCell.style.maxWidth = maxColumnWidth(null); - if (columnData.width) { - htmlCell.style.minWidth = htmlCell.style.maxWidth = maxColumnWidth(columnData.width); - } - htmlRow.appendChild(htmlCell); - }); - - for (var idx = 0; idx < columns.getPaddingCount(); idx++) { - var paddingCol = document.createElement("td"); - paddingCol.setAttribute("class", "pagedtable-padding-col"); - htmlRow.appendChild(paddingCol); - } - - if (columns.hasMoreRightColumns()) - htmlRow.appendChild(document.createElement("td")); - - fragment.appendChild(htmlRow); - }); - - for (var idxPadding = 0; idxPadding < page.getPaddingRows(); idxPadding++) { - var paddingRow = document.createElement("tr"); - - var paddingCellRow = document.createElement("td"); - paddingCellRow.innerHTML = " "; - paddingCellRow.setAttribute("colspan", "100%"); - paddingRow.appendChild(paddingCellRow); - - fragment.appendChild(paddingRow); - } - - if (typeof(clear) == "undefined" || clear) clearBody(); - tbody = document.createElement("tbody"); - tbody.appendChild(fragment); - - table.appendChild(tbody); - }; - - var getLabelInfo = function() { - var pageStart = page.getRowStart(); - var pageEnd = page.getRowEnd(); - var totalRows = data.length; - - var totalRowsLabel = options.rows.total ? options.rows.total : totalRows; - var totalRowsLabelFormat = totalRowsLabel.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,'); - - var infoText = (pageStart + 1) + "-" + pageEnd + " of " + totalRowsLabelFormat + " rows"; - if (totalRows < page.rows) { - infoText = totalRowsLabel + " row" + (totalRows != 1 ? "s" : ""); - } - if (columns.total > columns.visible) { - var totalColumnsLabel = options.columns.total ? options.columns.total : columns.total; - - infoText = infoText + " | " + (columns.number + 1) + "-" + - (Math.min(columns.number + columns.visible, columns.total)) + - " of " + totalColumnsLabel + " columns"; - } - - return infoText; - }; - - var clearFooter = function() { - footer = pagedTable.querySelectorAll("div.pagedtable-footer")[0]; - footer.innerHTML = ""; - - return footer; - }; - - var createPageLink = function(idxPage) { - var pageLink = document.createElement("a"); - pageLinkClass = idxPage === page.number ? "pagedtable-index pagedtable-index-current" : "pagedtable-index"; - pageLink.setAttribute("class", pageLinkClass); - pageLink.setAttribute("data-page-index", idxPage); - pageLink.onclick = function() { - page.setPageNumber(parseInt(this.getAttribute("data-page-index"))); - renderBody(); - renderFooter(); - - triggerOnChange(); - }; - - pageLink.appendChild(document.createTextNode(idxPage + 1)); - - return pageLink; - } - - var renderFooter = function() { - footer = clearFooter(); - - var next = document.createElement("a"); - next.appendChild(document.createTextNode("Next")); - next.onclick = function() { - page.setPageNumber(page.number + 1); - renderBody(); - renderFooter(); - - triggerOnChange(); - }; - if (data.length > page.rows) footer.appendChild(next); - - var pageNumbers = document.createElement("div"); - pageNumbers.setAttribute("class", "pagedtable-indexes"); - - var pageRange = page.getVisiblePageRange(); - - if (pageRange.first) { - var pageLink = createPageLink(0); - pageNumbers.appendChild(pageLink); - - var pageSeparator = document.createElement("div"); - pageSeparator.setAttribute("class", "pagedtable-index-separator-left"); - pageSeparator.appendChild(document.createTextNode("...")) - pageNumbers.appendChild(pageSeparator); - } - - for (var idxPage = pageRange.start; idxPage < pageRange.end; idxPage++) { - var pageLink = createPageLink(idxPage); - - pageNumbers.appendChild(pageLink); - } - - if (pageRange.last) { - var pageSeparator = document.createElement("div"); - pageSeparator.setAttribute("class", "pagedtable-index-separator-right"); - pageSeparator.appendChild(document.createTextNode("...")) - pageNumbers.appendChild(pageSeparator); - - var pageLink = createPageLink(page.total - 1); - pageNumbers.appendChild(pageLink); - } - - if (data.length > page.rows) footer.appendChild(pageNumbers); - - var previous = document.createElement("a"); - previous.appendChild(document.createTextNode("Previous")); - previous.onclick = function() { - page.setPageNumber(page.number - 1); - renderBody(); - renderFooter(); - - triggerOnChange(); - }; - if (data.length > page.rows) footer.appendChild(previous); - - var infoLabel = document.createElement("div"); - infoLabel.setAttribute("class", "pagedtable-info"); - infoLabel.setAttribute("title", getLabelInfo()); - infoLabel.appendChild(document.createTextNode(getLabelInfo())); - footer.appendChild(infoLabel); - - var enabledClass = "pagedtable-index-nav"; - var disabledClass = "pagedtable-index-nav pagedtable-index-nav-disabled"; - previous.setAttribute("class", page.number <= 0 ? disabledClass : enabledClass); - next.setAttribute("class", (page.number + 1) * page.rows >= data.length ? disabledClass : enabledClass); - }; - - var measuresCell = null; - - var renderMeasures = function() { - var measuresTable = document.createElement("table"); - measuresTable.style.visibility = "hidden"; - measuresTable.style.position = "absolute"; - measuresTable.style.whiteSpace = "nowrap"; - measuresTable.style.height = "auto"; - measuresTable.style.width = "auto"; - - var measuresRow = document.createElement("tr"); - measuresTable.appendChild(measuresRow); - - measuresCell = document.createElement("td"); - var sampleString = "ABCDEFGHIJ0123456789"; - measuresCell.appendChild(document.createTextNode(sampleString)); - - measuresRow.appendChild(measuresCell); - - tableDiv.appendChild(measuresTable); - } - - me.init = function() { - tableDiv = document.createElement("div"); - pagedTable.appendChild(tableDiv); - var pagedTableClass = data.length > 0 ? - "pagedtable pagedtable-not-empty" : - "pagedtable pagedtable-empty"; - - if (columns.total == 0 || (columns.emptyNames() && data.length == 0)) { - pagedTableClass = pagedTableClass + " pagedtable-empty-columns"; - } - - tableDiv.setAttribute("class", pagedTableClass); - - renderMeasures(); - measurer.calculate(measuresCell); - columns.calculateWidths(measurer.measures); - - table = document.createElement("table"); - table.setAttribute("cellspacing", "0"); - table.setAttribute("class", "table table-condensed"); - tableDiv.appendChild(table); - - table.appendChild(document.createElement("thead")); - - var footerDiv = document.createElement("div"); - footerDiv.setAttribute("class", "pagedtable-footer"); - tableDiv.appendChild(footerDiv); - - // if the host has not yet provided horizontal space, render hidden - if (tableDiv.clientWidth <= 0) { - tableDiv.style.opacity = "0"; - } - - me.render(); - - // retry seizing columns later if the host has not provided space - function retryFit() { - if (tableDiv.clientWidth <= 0) { - setTimeout(retryFit, 100); - } else { - me.render(); - triggerOnChange(); - } - } - if (tableDiv.clientWidth <= 0) { - retryFit(); - } - }; - - var registerWidths = function() { - columns.subset = columns.subset.map(function(column) { - column.width = columns.widths[column.name].inner; - return column; - }); - }; - - var parsePadding = function(value) { - return parseInt(value) >= 0 ? parseInt(value) : 0; - }; - - me.fixedHeight = function() { - return options.rows.max != null; - } - - me.fitRows = function() { - if (me.fixedHeight()) - return; - - measurer.calculate(measuresCell); - - var rows = options.rows.min !== null ? options.rows.min : 0; - var headerHeight = header !== null && header.offsetHeight > 0 ? header.offsetHeight : 0; - var footerHeight = footer !== null && footer.offsetHeight > 0 ? footer.offsetHeight : 0; - - if (pagedTable.offsetHeight > 0) { - var availableHeight = pagedTable.offsetHeight - headerHeight - footerHeight; - rows = Math.floor((availableHeight) / measurer.measures.height); - } - - rows = options.rows.min !== null ? Math.max(options.rows.min, rows) : rows; - - page.setRows(rows); - } - - // The goal of this function is to add as many columns as possible - // starting from left-to-right, when the right most limit is reached - // it tries to add columns from the left as well. - // - // When startBackwards is true columns are added from right-to-left - me.fitColumns = function(startBackwards) { - measurer.calculate(measuresCell); - columns.calculateWidths(measurer.measures); - - if (tableDiv.clientWidth > 0) { - tableDiv.style.opacity = 1; - } - - var visibleColumns = tableDiv.clientWidth <= 0 ? Math.max(columns.min, 1) : 1; - var columnNumber = columns.number; - var paddingCount = 0; - - // track a list of added columns as we build the visible ones to allow us - // to remove columns when they don't fit anymore. - var columnHistory = []; - - var lastTableHeight = 0; - var backwards = startBackwards; - - var tableDivStyle = window.getComputedStyle(tableDiv, null); - var tableDivPadding = parsePadding(tableDivStyle.paddingLeft) + - parsePadding(tableDivStyle.paddingRight); - - var addPaddingCol = false; - var currentWidth = 0; - - while (true) { - columns.setVisibleColumns(columnNumber, visibleColumns, paddingCount); - currentWidth = columns.getWidth(); - - if (tableDiv.clientWidth - tableDivPadding < currentWidth) { - break; - } - - columnHistory.push({ - columnNumber: columnNumber, - visibleColumns: visibleColumns, - paddingCount: paddingCount - }); - - if (columnHistory.length > 100) { - console.error("More than 100 tries to fit columns, aborting"); - break; - } - - if (columns.max !== null && - columns.visible + columns.getPaddingCount() >= columns.max) { - break; - } - - // if we run out of right-columns - if (!backwards && columnNumber + columns.visible >= columns.total) { - // if we started adding right-columns, try adding left-columns - if (!startBackwards && columnNumber > 0) { - backwards = true; - } - else if (columns.min === null || visibleColumns + columns.getPaddingCount() >= columns.min) { - break; - } - else { - paddingCount = paddingCount + 1; - } - } - - // if we run out of left-columns - if (backwards && columnNumber == 0) { - // if we started adding left-columns, try adding right-columns - if (startBackwards && columnNumber + columns.visible < columns.total) { - backwards = false; - } - else if (columns.min === null || visibleColumns + columns.getPaddingCount() >= columns.min) { - break; - } - else { - paddingCount = paddingCount + 1; - } - } - - // when moving backwards try fitting left columns first - if (backwards && columnNumber > 0) { - columnNumber = columnNumber - 1; - } - - if (columnNumber + visibleColumns < columns.total) { - visibleColumns = visibleColumns + 1; - } - } - - var lastRenderableColumn = { - columnNumber: columnNumber, - visibleColumns: visibleColumns, - paddingCount: paddingCount - }; - - if (columnHistory.length > 0) { - lastRenderableColumn = columnHistory[columnHistory.length - 1]; - } - - columns.setVisibleColumns( - lastRenderableColumn.columnNumber, - lastRenderableColumn.visibleColumns, - lastRenderableColumn.paddingCount); - - if (pagedTable.offsetWidth > 0) { - page.setVisiblePages(Math.max(Math.ceil(1.0 * (pagedTable.offsetWidth - 250) / 40), 2)); - } - - registerWidths(); - }; - - me.fit = function(startBackwards) { - me.fitRows(); - me.fitColumns(startBackwards); - } - - me.render = function() { - me.fitColumns(false); - - // render header/footer to measure height accurately - renderHeader(); - renderFooter(); - - me.fitRows(); - renderBody(); - - // re-render footer to match new rows - renderFooter(); - } - - var resizeLastWidth = -1; - var resizeLastHeight = -1; - var resizeNewWidth = -1; - var resizeNewHeight = -1; - var resizePending = false; - - me.resize = function(newWidth, newHeight) { - - function resizeDelayed() { - resizePending = false; - - if ( - (resizeNewWidth !== resizeLastWidth) || - (!me.fixedHeight() && resizeNewHeight !== resizeLastHeight) - ) { - resizeLastWidth = resizeNewWidth; - resizeLastHeight = resizeNewHeight; - - setTimeout(resizeDelayed, 200); - resizePending = true; - } else { - me.render(); - triggerOnChange(); - - resizeLastWidth = -1; - resizeLastHeight = -1; - } - } - - resizeNewWidth = newWidth; - resizeNewHeight = newHeight; - - if (!resizePending) resizeDelayed(); - }; -}; - -var PagedTableDoc; -(function (PagedTableDoc) { - var allPagedTables = []; - - PagedTableDoc.initAll = function() { - allPagedTables = []; - - var pagedTables = [].slice.call(document.querySelectorAll('[data-pagedtable="false"],[data-pagedtable=""]')); - pagedTables.forEach(function(pagedTable, idx) { - pagedTable.setAttribute("data-pagedtable", "true"); - pagedTable.setAttribute("pagedtable-page", 0); - pagedTable.setAttribute("class", "pagedtable-wrapper"); - - var pagedTableInstance = new PagedTable(pagedTable); - pagedTableInstance.init(); - - allPagedTables.push(pagedTableInstance); - }); - }; - - PagedTableDoc.resizeAll = function() { - allPagedTables.forEach(function(pagedTable) { - pagedTable.render(); - }); - }; - - window.addEventListener("resize", PagedTableDoc.resizeAll); - - return PagedTableDoc; -})(PagedTableDoc || (PagedTableDoc = {})); - -window.onload = function() { - PagedTableDoc.initAll(); -}; diff --git a/choosing_files/libs/quarto-html/anchor.min.js b/choosing_files/libs/quarto-html/anchor.min.js deleted file mode 100644 index 1c2b86f..0000000 --- a/choosing_files/libs/quarto-html/anchor.min.js +++ /dev/null @@ -1,9 +0,0 @@ -// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat -// -// AnchorJS - v4.3.1 - 2021-04-17 -// https://www.bryanbraun.com/anchorjs/ -// Copyright (c) 2021 Bryan Braun; Licensed MIT -// -// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat -!function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(this,function(){"use strict";return function(A){function d(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function w(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],d(this.options),this.isTouchDevice=function(){return Boolean("ontouchstart"in window||window.TouchEvent||window.DocumentTouch&&document instanceof DocumentTouch)},this.add=function(A){var e,t,o,i,n,s,a,c,r,l,h,u,p=[];if(d(this.options),"touch"===(l=this.options.visible)&&(l=this.isTouchDevice()?"always":"hover"),0===(e=w(A=A||"h2, h3, h4, h5, h6")).length)return this;for(null===document.head.querySelector("style.anchorjs")&&((u=document.createElement("style")).className="anchorjs",u.appendChild(document.createTextNode("")),void 0===(A=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(u):document.head.insertBefore(u,A),u.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",u.sheet.cssRules.length),u.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); -// @license-end \ No newline at end of file diff --git a/choosing_files/libs/quarto-html/light-border.css b/choosing_files/libs/quarto-html/light-border.css deleted file mode 100644 index 2b25c61..0000000 --- a/choosing_files/libs/quarto-html/light-border.css +++ /dev/null @@ -1 +0,0 @@ -.tippy-box[data-theme~=light-border]{background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,8,16,.15);color:#333;box-shadow:0 4px 14px -2px rgba(0,8,16,.08)}.tippy-box[data-theme~=light-border]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light-border]>.tippy-arrow:after,.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=light-border]>.tippy-arrow:after{border-color:transparent;border-style:solid}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:after{border-top-color:rgba(0,8,16,.2);border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:rgba(0,8,16,.2);border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:after{border-left-color:rgba(0,8,16,.2);border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,.2)}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow{fill:#fff}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px} \ No newline at end of file diff --git a/choosing_files/libs/quarto-html/popper.min.js b/choosing_files/libs/quarto-html/popper.min.js deleted file mode 100644 index 2269d66..0000000 --- a/choosing_files/libs/quarto-html/popper.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @popperjs/core v2.11.4 - MIT License - */ - -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); - diff --git a/choosing_files/libs/quarto-html/quarto-html.min.css b/choosing_files/libs/quarto-html/quarto-html.min.css deleted file mode 100644 index c2857c3..0000000 --- a/choosing_files/libs/quarto-html/quarto-html.min.css +++ /dev/null @@ -1 +0,0 @@ -/*# sourceMappingURL=0a6b880beb84f9b6f36107a76f82c5b1.css.map */ diff --git a/choosing_files/libs/quarto-html/quarto-syntax-highlighting.css b/choosing_files/libs/quarto-html/quarto-syntax-highlighting.css deleted file mode 100644 index d9fd98f..0000000 --- a/choosing_files/libs/quarto-html/quarto-syntax-highlighting.css +++ /dev/null @@ -1,203 +0,0 @@ -/* quarto syntax highlight colors */ -:root { - --quarto-hl-ot-color: #003B4F; - --quarto-hl-at-color: #657422; - --quarto-hl-ss-color: #20794D; - --quarto-hl-an-color: #5E5E5E; - --quarto-hl-fu-color: #4758AB; - --quarto-hl-st-color: #20794D; - --quarto-hl-cf-color: #003B4F; - --quarto-hl-op-color: #5E5E5E; - --quarto-hl-er-color: #AD0000; - --quarto-hl-bn-color: #AD0000; - --quarto-hl-al-color: #AD0000; - --quarto-hl-va-color: #111111; - --quarto-hl-bu-color: inherit; - --quarto-hl-ex-color: inherit; - --quarto-hl-pp-color: #AD0000; - --quarto-hl-in-color: #5E5E5E; - --quarto-hl-vs-color: #20794D; - --quarto-hl-wa-color: #5E5E5E; - --quarto-hl-do-color: #5E5E5E; - --quarto-hl-im-color: #00769E; - --quarto-hl-ch-color: #20794D; - --quarto-hl-dt-color: #AD0000; - --quarto-hl-fl-color: #AD0000; - --quarto-hl-co-color: #5E5E5E; - --quarto-hl-cv-color: #5E5E5E; - --quarto-hl-cn-color: #8f5902; - --quarto-hl-sc-color: #5E5E5E; - --quarto-hl-dv-color: #AD0000; - --quarto-hl-kw-color: #003B4F; -} - -/* other quarto variables */ -:root { - --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; -} - -pre > code.sourceCode > span { - color: #003B4F; -} - -code span { - color: #003B4F; -} - -code.sourceCode > span { - color: #003B4F; -} - -div.sourceCode, -div.sourceCode pre.sourceCode { - color: #003B4F; -} - -code span.ot { - color: #003B4F; - font-style: inherit; -} - -code span.at { - color: #657422; - font-style: inherit; -} - -code span.ss { - color: #20794D; - font-style: inherit; -} - -code span.an { - color: #5E5E5E; - font-style: inherit; -} - -code span.fu { - color: #4758AB; - font-style: inherit; -} - -code span.st { - color: #20794D; - font-style: inherit; -} - -code span.cf { - color: #003B4F; - font-style: inherit; -} - -code span.op { - color: #5E5E5E; - font-style: inherit; -} - -code span.er { - color: #AD0000; - font-style: inherit; -} - -code span.bn { - color: #AD0000; - font-style: inherit; -} - -code span.al { - color: #AD0000; - font-style: inherit; -} - -code span.va { - color: #111111; - font-style: inherit; -} - -code span.bu { - font-style: inherit; -} - -code span.ex { - font-style: inherit; -} - -code span.pp { - color: #AD0000; - font-style: inherit; -} - -code span.in { - color: #5E5E5E; - font-style: inherit; -} - -code span.vs { - color: #20794D; - font-style: inherit; -} - -code span.wa { - color: #5E5E5E; - font-style: italic; -} - -code span.do { - color: #5E5E5E; - font-style: italic; -} - -code span.im { - color: #00769E; - font-style: inherit; -} - -code span.ch { - color: #20794D; - font-style: inherit; -} - -code span.dt { - color: #AD0000; - font-style: inherit; -} - -code span.fl { - color: #AD0000; - font-style: inherit; -} - -code span.co { - color: #5E5E5E; - font-style: inherit; -} - -code span.cv { - color: #5E5E5E; - font-style: italic; -} - -code span.cn { - color: #8f5902; - font-style: inherit; -} - -code span.sc { - color: #5E5E5E; - font-style: inherit; -} - -code span.dv { - color: #AD0000; - font-style: inherit; -} - -code span.kw { - color: #003B4F; - font-style: inherit; -} - -.prevent-inlining { - content: " { - const sibling = el.previousElementSibling; - if (sibling && sibling.tagName === "A") { - return sibling.classList.contains("active"); - } else { - return false; - } - }; - - // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) - function fireSlideEnter(e) { - const event = window.document.createEvent("Event"); - event.initEvent("slideenter", true, true); - window.document.dispatchEvent(event); - } - const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); - tabs.forEach((tab) => { - tab.addEventListener("shown.bs.tab", fireSlideEnter); - }); - - // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) - document.addEventListener("tabby", fireSlideEnter, false); - - // Track scrolling and mark TOC links as active - // get table of contents and sidebar (bail if we don't have at least one) - const tocLinks = tocEl - ? [...tocEl.querySelectorAll("a[data-scroll-target]")] - : []; - const makeActive = (link) => tocLinks[link].classList.add("active"); - const removeActive = (link) => tocLinks[link].classList.remove("active"); - const removeAllActive = () => - [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); - - // activate the anchor for a section associated with this TOC entry - tocLinks.forEach((link) => { - link.addEventListener("click", () => { - if (link.href.indexOf("#") !== -1) { - const anchor = link.href.split("#")[1]; - const heading = window.document.querySelector( - `[data-anchor-id=${anchor}]` - ); - if (heading) { - // Add the class - heading.classList.add("reveal-anchorjs-link"); - - // function to show the anchor - const handleMouseout = () => { - heading.classList.remove("reveal-anchorjs-link"); - heading.removeEventListener("mouseout", handleMouseout); - }; - - // add a function to clear the anchor when the user mouses out of it - heading.addEventListener("mouseout", handleMouseout); - } - } - }); - }); - - const sections = tocLinks.map((link) => { - const target = link.getAttribute("data-scroll-target"); - if (target.startsWith("#")) { - return window.document.getElementById(decodeURI(`${target.slice(1)}`)); - } else { - return window.document.querySelector(decodeURI(`${target}`)); - } - }); - - const sectionMargin = 200; - let currentActive = 0; - // track whether we've initialized state the first time - let init = false; - - const updateActiveLink = () => { - // The index from bottom to top (e.g. reversed list) - let sectionIndex = -1; - if ( - window.innerHeight + window.pageYOffset >= - window.document.body.offsetHeight - ) { - sectionIndex = 0; - } else { - sectionIndex = [...sections].reverse().findIndex((section) => { - if (section) { - return window.pageYOffset >= section.offsetTop - sectionMargin; - } else { - return false; - } - }); - } - if (sectionIndex > -1) { - const current = sections.length - sectionIndex - 1; - if (current !== currentActive) { - removeAllActive(); - currentActive = current; - makeActive(current); - if (init) { - window.dispatchEvent(sectionChanged); - } - init = true; - } - } - }; - - const inHiddenRegion = (top, bottom, hiddenRegions) => { - for (const region of hiddenRegions) { - if (top <= region.bottom && bottom >= region.top) { - return true; - } - } - return false; - }; - - const categorySelector = "header.quarto-title-block .quarto-category"; - const activateCategories = (href) => { - // Find any categories - // Surround them with a link pointing back to: - // #category=Authoring - try { - const categoryEls = window.document.querySelectorAll(categorySelector); - for (const categoryEl of categoryEls) { - const categoryText = categoryEl.textContent; - if (categoryText) { - const link = `${href}#category=${encodeURIComponent(categoryText)}`; - const linkEl = window.document.createElement("a"); - linkEl.setAttribute("href", link); - for (const child of categoryEl.childNodes) { - linkEl.append(child); - } - categoryEl.appendChild(linkEl); - } - } - } catch { - // Ignore errors - } - }; - function hasTitleCategories() { - return window.document.querySelector(categorySelector) !== null; - } - - function offsetRelativeUrl(url) { - const offset = getMeta("quarto:offset"); - return offset ? offset + url : url; - } - - function offsetAbsoluteUrl(url) { - const offset = getMeta("quarto:offset"); - const baseUrl = new URL(offset, window.location); - - const projRelativeUrl = url.replace(baseUrl, ""); - if (projRelativeUrl.startsWith("/")) { - return projRelativeUrl; - } else { - return "/" + projRelativeUrl; - } - } - - // read a meta tag value - function getMeta(metaName) { - const metas = window.document.getElementsByTagName("meta"); - for (let i = 0; i < metas.length; i++) { - if (metas[i].getAttribute("name") === metaName) { - return metas[i].getAttribute("content"); - } - } - return ""; - } - - async function findAndActivateCategories() { - const currentPagePath = offsetAbsoluteUrl(window.location.href); - const response = await fetch(offsetRelativeUrl("listings.json")); - if (response.status == 200) { - return response.json().then(function (listingPaths) { - const listingHrefs = []; - for (const listingPath of listingPaths) { - const pathWithoutLeadingSlash = listingPath.listing.substring(1); - for (const item of listingPath.items) { - if ( - item === currentPagePath || - item === currentPagePath + "index.html" - ) { - // Resolve this path against the offset to be sure - // we already are using the correct path to the listing - // (this adjusts the listing urls to be rooted against - // whatever root the page is actually running against) - const relative = offsetRelativeUrl(pathWithoutLeadingSlash); - const baseUrl = window.location; - const resolvedPath = new URL(relative, baseUrl); - listingHrefs.push(resolvedPath.pathname); - break; - } - } - } - - // Look up the tree for a nearby linting and use that if we find one - const nearestListing = findNearestParentListing( - offsetAbsoluteUrl(window.location.pathname), - listingHrefs - ); - if (nearestListing) { - activateCategories(nearestListing); - } else { - // See if the referrer is a listing page for this item - const referredRelativePath = offsetAbsoluteUrl(document.referrer); - const referrerListing = listingHrefs.find((listingHref) => { - const isListingReferrer = - listingHref === referredRelativePath || - listingHref === referredRelativePath + "index.html"; - return isListingReferrer; - }); - - if (referrerListing) { - // Try to use the referrer if possible - activateCategories(referrerListing); - } else if (listingHrefs.length > 0) { - // Otherwise, just fall back to the first listing - activateCategories(listingHrefs[0]); - } - } - }); - } - } - if (hasTitleCategories()) { - findAndActivateCategories(); - } - - const findNearestParentListing = (href, listingHrefs) => { - if (!href || !listingHrefs) { - return undefined; - } - // Look up the tree for a nearby linting and use that if we find one - const relativeParts = href.substring(1).split("/"); - while (relativeParts.length > 0) { - const path = relativeParts.join("/"); - for (const listingHref of listingHrefs) { - if (listingHref.startsWith(path)) { - return listingHref; - } - } - relativeParts.pop(); - } - - return undefined; - }; - - const manageSidebarVisiblity = (el, placeholderDescriptor) => { - let isVisible = true; - let elRect; - - return (hiddenRegions) => { - if (el === null) { - return; - } - - // Find the last element of the TOC - const lastChildEl = el.lastElementChild; - - if (lastChildEl) { - // Converts the sidebar to a menu - const convertToMenu = () => { - for (const child of el.children) { - child.style.opacity = 0; - child.style.overflow = "hidden"; - } - - nexttick(() => { - const toggleContainer = window.document.createElement("div"); - toggleContainer.style.width = "100%"; - toggleContainer.classList.add("zindex-over-content"); - toggleContainer.classList.add("quarto-sidebar-toggle"); - toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom - toggleContainer.id = placeholderDescriptor.id; - toggleContainer.style.position = "fixed"; - - const toggleIcon = window.document.createElement("i"); - toggleIcon.classList.add("quarto-sidebar-toggle-icon"); - toggleIcon.classList.add("bi"); - toggleIcon.classList.add("bi-caret-down-fill"); - - const toggleTitle = window.document.createElement("div"); - const titleEl = window.document.body.querySelector( - placeholderDescriptor.titleSelector - ); - if (titleEl) { - toggleTitle.append( - titleEl.textContent || titleEl.innerText, - toggleIcon - ); - } - toggleTitle.classList.add("zindex-over-content"); - toggleTitle.classList.add("quarto-sidebar-toggle-title"); - toggleContainer.append(toggleTitle); - - const toggleContents = window.document.createElement("div"); - toggleContents.classList = el.classList; - toggleContents.classList.add("zindex-over-content"); - toggleContents.classList.add("quarto-sidebar-toggle-contents"); - for (const child of el.children) { - if (child.id === "toc-title") { - continue; - } - - const clone = child.cloneNode(true); - clone.style.opacity = 1; - clone.style.display = null; - toggleContents.append(clone); - } - toggleContents.style.height = "0px"; - const positionToggle = () => { - // position the element (top left of parent, same width as parent) - if (!elRect) { - elRect = el.getBoundingClientRect(); - } - toggleContainer.style.left = `${elRect.left}px`; - toggleContainer.style.top = `${elRect.top}px`; - toggleContainer.style.width = `${elRect.width}px`; - }; - positionToggle(); - - toggleContainer.append(toggleContents); - el.parentElement.prepend(toggleContainer); - - // Process clicks - let tocShowing = false; - // Allow the caller to control whether this is dismissed - // when it is clicked (e.g. sidebar navigation supports - // opening and closing the nav tree, so don't dismiss on click) - const clickEl = placeholderDescriptor.dismissOnClick - ? toggleContainer - : toggleTitle; - - const closeToggle = () => { - if (tocShowing) { - toggleContainer.classList.remove("expanded"); - toggleContents.style.height = "0px"; - tocShowing = false; - } - }; - - // Get rid of any expanded toggle if the user scrolls - window.document.addEventListener( - "scroll", - throttle(() => { - closeToggle(); - }, 50) - ); - - // Handle positioning of the toggle - window.addEventListener( - "resize", - throttle(() => { - elRect = undefined; - positionToggle(); - }, 50) - ); - - window.addEventListener("quarto-hrChanged", () => { - elRect = undefined; - }); - - // Process the click - clickEl.onclick = () => { - if (!tocShowing) { - toggleContainer.classList.add("expanded"); - toggleContents.style.height = null; - tocShowing = true; - } else { - closeToggle(); - } - }; - }); - }; - - // Converts a sidebar from a menu back to a sidebar - const convertToSidebar = () => { - for (const child of el.children) { - child.style.opacity = 1; - child.style.overflow = null; - } - - const placeholderEl = window.document.getElementById( - placeholderDescriptor.id - ); - if (placeholderEl) { - placeholderEl.remove(); - } - - el.classList.remove("rollup"); - }; - - if (isReaderMode()) { - convertToMenu(); - isVisible = false; - } else { - // Find the top and bottom o the element that is being managed - const elTop = el.offsetTop; - const elBottom = - elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; - - if (!isVisible) { - // If the element is current not visible reveal if there are - // no conflicts with overlay regions - if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { - convertToSidebar(); - isVisible = true; - } - } else { - // If the element is visible, hide it if it conflicts with overlay regions - // and insert a placeholder toggle (or if we're in reader mode) - if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { - convertToMenu(); - isVisible = false; - } - } - } - } - }; - }; - - // Find any conflicting margin elements and add margins to the - // top to prevent overlap - const marginChildren = window.document.querySelectorAll( - ".column-margin.column-container > * " - ); - - const layoutMarginEls = () => { - let lastBottom = 0; - for (const marginChild of marginChildren) { - if (marginChild.offsetParent !== null) { - // clear the top margin so we recompute it - marginChild.style.marginTop = null; - const top = marginChild.getBoundingClientRect().top + window.scrollY; - if (top < lastBottom) { - const margin = lastBottom - top; - marginChild.style.marginTop = `${margin}px`; - } - const styles = window.getComputedStyle(marginChild); - const marginTop = parseFloat(styles["marginTop"]); - lastBottom = - top + marginChild.getBoundingClientRect().height + marginTop; - } - } - }; - nexttick(layoutMarginEls); - - const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); - for (const tabEl of tabEls) { - const id = tabEl.getAttribute("data-bs-target"); - if (id) { - const columnEl = document.querySelector( - `${id} .column-margin, .tabset-margin-content` - ); - if (columnEl) - tabEl.addEventListener("shown.bs.tab", function (event) { - - const el = event.srcElement; - if (el) { - const visibleCls = `${el.id}-margin-content`; - // walk up until we find a parent tabset - let panelTabsetEl = el.parentElement; - while (panelTabsetEl) { - if (panelTabsetEl.classList.contains("panel-tabset")) { - break; - } - panelTabsetEl = panelTabsetEl.parentElement; - } - - if (panelTabsetEl) { - const prevSib = panelTabsetEl.previousElementSibling; - if ( - prevSib && - prevSib.classList.contains("tabset-margin-container") - ) { - const childNodes = prevSib.querySelectorAll( - ".tabset-margin-content" - ); - for (const childEl of childNodes) { - if (childEl.classList.contains(visibleCls)) { - childEl.classList.remove("collapse"); - } else { - childEl.classList.add("collapse"); - } - } - } - } - } - - layoutMarginEls(); - }); - } - } - - // Manage the visibility of the toc and the sidebar - const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { - id: "quarto-toc-toggle", - titleSelector: "#toc-title", - dismissOnClick: true, - }); - const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { - id: "quarto-sidebarnav-toggle", - titleSelector: ".title", - dismissOnClick: false, - }); - let tocLeftScrollVisibility; - if (leftTocEl) { - tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { - id: "quarto-lefttoc-toggle", - titleSelector: "#toc-title", - dismissOnClick: true, - }); - } - - // Find the first element that uses formatting in special columns - const conflictingEls = window.document.body.querySelectorAll( - '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' - ); - - // Filter all the possibly conflicting elements into ones - // the do conflict on the left or ride side - const arrConflictingEls = Array.from(conflictingEls); - const leftSideConflictEls = arrConflictingEls.filter((el) => { - if (el.tagName === "ASIDE") { - return false; - } - return Array.from(el.classList).find((className) => { - return ( - className !== "column-body" && - className.startsWith("column-") && - !className.endsWith("right") && - !className.endsWith("container") && - className !== "column-margin" - ); - }); - }); - const rightSideConflictEls = arrConflictingEls.filter((el) => { - if (el.tagName === "ASIDE") { - return true; - } - - const hasMarginCaption = Array.from(el.classList).find((className) => { - return className == "margin-caption"; - }); - if (hasMarginCaption) { - return true; - } - - return Array.from(el.classList).find((className) => { - return ( - className !== "column-body" && - !className.endsWith("container") && - className.startsWith("column-") && - !className.endsWith("left") - ); - }); - }); - - const kOverlapPaddingSize = 10; - function toRegions(els) { - return els.map((el) => { - const boundRect = el.getBoundingClientRect(); - const top = - boundRect.top + - document.documentElement.scrollTop - - kOverlapPaddingSize; - return { - top, - bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, - }; - }); - } - - let hasObserved = false; - const visibleItemObserver = (els) => { - let visibleElements = [...els]; - const intersectionObserver = new IntersectionObserver( - (entries, _observer) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - if (visibleElements.indexOf(entry.target) === -1) { - visibleElements.push(entry.target); - } - } else { - visibleElements = visibleElements.filter((visibleEntry) => { - return visibleEntry !== entry; - }); - } - }); - - if (!hasObserved) { - hideOverlappedSidebars(); - } - hasObserved = true; - }, - {} - ); - els.forEach((el) => { - intersectionObserver.observe(el); - }); - - return { - getVisibleEntries: () => { - return visibleElements; - }, - }; - }; - - const rightElementObserver = visibleItemObserver(rightSideConflictEls); - const leftElementObserver = visibleItemObserver(leftSideConflictEls); - - const hideOverlappedSidebars = () => { - marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); - sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); - if (tocLeftScrollVisibility) { - tocLeftScrollVisibility( - toRegions(leftElementObserver.getVisibleEntries()) - ); - } - }; - - window.quartoToggleReader = () => { - // Applies a slow class (or removes it) - // to update the transition speed - const slowTransition = (slow) => { - const manageTransition = (id, slow) => { - const el = document.getElementById(id); - if (el) { - if (slow) { - el.classList.add("slow"); - } else { - el.classList.remove("slow"); - } - } - }; - - manageTransition("TOC", slow); - manageTransition("quarto-sidebar", slow); - }; - const readerMode = !isReaderMode(); - setReaderModeValue(readerMode); - - // If we're entering reader mode, slow the transition - if (readerMode) { - slowTransition(readerMode); - } - highlightReaderToggle(readerMode); - hideOverlappedSidebars(); - - // If we're exiting reader mode, restore the non-slow transition - if (!readerMode) { - slowTransition(!readerMode); - } - }; - - const highlightReaderToggle = (readerMode) => { - const els = document.querySelectorAll(".quarto-reader-toggle"); - if (els) { - els.forEach((el) => { - if (readerMode) { - el.classList.add("reader"); - } else { - el.classList.remove("reader"); - } - }); - } - }; - - const setReaderModeValue = (val) => { - if (window.location.protocol !== "file:") { - window.localStorage.setItem("quarto-reader-mode", val); - } else { - localReaderMode = val; - } - }; - - const isReaderMode = () => { - if (window.location.protocol !== "file:") { - return window.localStorage.getItem("quarto-reader-mode") === "true"; - } else { - return localReaderMode; - } - }; - let localReaderMode = null; - - const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); - const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; - - // Walk the TOC and collapse/expand nodes - // Nodes are expanded if: - // - they are top level - // - they have children that are 'active' links - // - they are directly below an link that is 'active' - const walk = (el, depth) => { - // Tick depth when we enter a UL - if (el.tagName === "UL") { - depth = depth + 1; - } - - // It this is active link - let isActiveNode = false; - if (el.tagName === "A" && el.classList.contains("active")) { - isActiveNode = true; - } - - // See if there is an active child to this element - let hasActiveChild = false; - for (child of el.children) { - hasActiveChild = walk(child, depth) || hasActiveChild; - } - - // Process the collapse state if this is an UL - if (el.tagName === "UL") { - if (tocOpenDepth === -1 && depth > 1) { - el.classList.add("collapse"); - } else if ( - depth <= tocOpenDepth || - hasActiveChild || - prevSiblingIsActiveLink(el) - ) { - el.classList.remove("collapse"); - } else { - el.classList.add("collapse"); - } - - // untick depth when we leave a UL - depth = depth - 1; - } - return hasActiveChild || isActiveNode; - }; - - // walk the TOC and expand / collapse any items that should be shown - - if (tocEl) { - walk(tocEl, 0); - updateActiveLink(); - } - - // Throttle the scroll event and walk peridiocally - window.document.addEventListener( - "scroll", - throttle(() => { - if (tocEl) { - updateActiveLink(); - walk(tocEl, 0); - } - if (!isReaderMode()) { - hideOverlappedSidebars(); - } - }, 5) - ); - window.addEventListener( - "resize", - throttle(() => { - if (!isReaderMode()) { - hideOverlappedSidebars(); - } - }, 10) - ); - hideOverlappedSidebars(); - highlightReaderToggle(isReaderMode()); -}); - -// grouped tabsets -window.addEventListener("pageshow", (_event) => { - function getTabSettings() { - const data = localStorage.getItem("quarto-persistent-tabsets-data"); - if (!data) { - localStorage.setItem("quarto-persistent-tabsets-data", "{}"); - return {}; - } - if (data) { - return JSON.parse(data); - } - } - - function setTabSettings(data) { - localStorage.setItem( - "quarto-persistent-tabsets-data", - JSON.stringify(data) - ); - } - - function setTabState(groupName, groupValue) { - const data = getTabSettings(); - data[groupName] = groupValue; - setTabSettings(data); - } - - function toggleTab(tab, active) { - const tabPanelId = tab.getAttribute("aria-controls"); - const tabPanel = document.getElementById(tabPanelId); - if (active) { - tab.classList.add("active"); - tabPanel.classList.add("active"); - } else { - tab.classList.remove("active"); - tabPanel.classList.remove("active"); - } - } - - function toggleAll(selectedGroup, selectorsToSync) { - for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { - const active = selectedGroup === thisGroup; - for (const tab of tabs) { - toggleTab(tab, active); - } - } - } - - function findSelectorsToSyncByLanguage() { - const result = {}; - const tabs = Array.from( - document.querySelectorAll(`div[data-group] a[id^='tabset-']`) - ); - for (const item of tabs) { - const div = item.parentElement.parentElement.parentElement; - const group = div.getAttribute("data-group"); - if (!result[group]) { - result[group] = {}; - } - const selectorsToSync = result[group]; - const value = item.innerHTML; - if (!selectorsToSync[value]) { - selectorsToSync[value] = []; - } - selectorsToSync[value].push(item); - } - return result; - } - - function setupSelectorSync() { - const selectorsToSync = findSelectorsToSyncByLanguage(); - Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { - Object.entries(tabSetsByValue).forEach(([value, items]) => { - items.forEach((item) => { - item.addEventListener("click", (_event) => { - setTabState(group, value); - toggleAll(value, selectorsToSync[group]); - }); - }); - }); - }); - return selectorsToSync; - } - - const selectorsToSync = setupSelectorSync(); - for (const [group, selectedName] of Object.entries(getTabSettings())) { - const selectors = selectorsToSync[group]; - // it's possible that stale state gives us empty selections, so we explicitly check here. - if (selectors) { - toggleAll(selectedName, selectors); - } - } -}); - -function throttle(func, wait) { - let waiting = false; - return function () { - if (!waiting) { - func.apply(this, arguments); - waiting = true; - setTimeout(function () { - waiting = false; - }, wait); - } - }; -} - -function nexttick(func) { - return setTimeout(func, 0); -} diff --git a/choosing_files/libs/quarto-html/tabby.min.js b/choosing_files/libs/quarto-html/tabby.min.js deleted file mode 100644 index 4f44c7d..0000000 --- a/choosing_files/libs/quarto-html/tabby.min.js +++ /dev/null @@ -1,418 +0,0 @@ -(function (root, factory) { - if (typeof define === "function" && define.amd) { - define([], function () { - return factory(root); - }); - } else if (typeof exports === "object") { - module.exports = factory(root); - } else { - root.Tabby = factory(root); - } -})( - typeof global !== "undefined" - ? global - : typeof window !== "undefined" - ? window - : this, - function (window) { - "use strict"; - - // - // Variables - // - - var defaults = { - idPrefix: "tabby-toggle_", - default: "[data-tabby-default]", - }; - - // - // Methods - // - - /** - * Merge two or more objects together. - * @param {Object} objects The objects to merge together - * @returns {Object} Merged values of defaults and options - */ - var extend = function () { - var merged = {}; - Array.prototype.forEach.call(arguments, function (obj) { - for (var key in obj) { - if (!obj.hasOwnProperty(key)) return; - merged[key] = obj[key]; - } - }); - return merged; - }; - - /** - * Emit a custom event - * @param {String} type The event type - * @param {Node} tab The tab to attach the event to - * @param {Node} details Details about the event - */ - var emitEvent = function (tab, details) { - // Create a new event - var event; - if (typeof window.CustomEvent === "function") { - event = new CustomEvent("tabby", { - bubbles: true, - cancelable: true, - detail: details, - }); - } else { - event = document.createEvent("CustomEvent"); - event.initCustomEvent("tabby", true, true, details); - } - - // Dispatch the event - tab.dispatchEvent(event); - }; - - var focusHandler = function (event) { - toggle(event.target); - }; - - var getKeyboardFocusableElements = function (element) { - return [ - ...element.querySelectorAll( - 'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])' - ), - ].filter( - (el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden") - ); - }; - - /** - * Remove roles and attributes from a tab and its content - * @param {Node} tab The tab - * @param {Node} content The tab content - * @param {Object} settings User settings and options - */ - var destroyTab = function (tab, content, settings) { - // Remove the generated ID - if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) { - tab.id = ""; - } - - // remove event listener - tab.removeEventListener("focus", focusHandler, true); - - // Remove roles - tab.removeAttribute("role"); - tab.removeAttribute("aria-controls"); - tab.removeAttribute("aria-selected"); - tab.removeAttribute("tabindex"); - tab.closest("li").removeAttribute("role"); - content.removeAttribute("role"); - content.removeAttribute("aria-labelledby"); - content.removeAttribute("hidden"); - }; - - /** - * Add the required roles and attributes to a tab and its content - * @param {Node} tab The tab - * @param {Node} content The tab content - * @param {Object} settings User settings and options - */ - var setupTab = function (tab, content, settings) { - // Give tab an ID if it doesn't already have one - if (!tab.id) { - tab.id = settings.idPrefix + content.id; - } - - // Add roles - tab.setAttribute("role", "tab"); - tab.setAttribute("aria-controls", content.id); - tab.closest("li").setAttribute("role", "presentation"); - content.setAttribute("role", "tabpanel"); - content.setAttribute("aria-labelledby", tab.id); - - // Add selected state - if (tab.matches(settings.default)) { - tab.setAttribute("aria-selected", "true"); - } else { - tab.setAttribute("aria-selected", "false"); - content.setAttribute("hidden", "hidden"); - } - - // add focus event listender - tab.addEventListener("focus", focusHandler); - }; - - /** - * Hide a tab and its content - * @param {Node} newTab The new tab that's replacing it - */ - var hide = function (newTab) { - // Variables - var tabGroup = newTab.closest('[role="tablist"]'); - if (!tabGroup) return {}; - var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]'); - if (!tab) return {}; - var content = document.querySelector(tab.hash); - - // Hide the tab - tab.setAttribute("aria-selected", "false"); - - // Hide the content - if (!content) return { previousTab: tab }; - content.setAttribute("hidden", "hidden"); - - // Return the hidden tab and content - return { - previousTab: tab, - previousContent: content, - }; - }; - - /** - * Show a tab and its content - * @param {Node} tab The tab - * @param {Node} content The tab content - */ - var show = function (tab, content) { - tab.setAttribute("aria-selected", "true"); - content.removeAttribute("hidden"); - tab.focus(); - }; - - /** - * Toggle a new tab - * @param {Node} tab The tab to show - */ - var toggle = function (tab) { - // Make sure there's a tab to toggle and it's not already active - if (!tab || tab.getAttribute("aria-selected") == "true") return; - - // Variables - var content = document.querySelector(tab.hash); - if (!content) return; - - // Hide active tab and content - var details = hide(tab); - - // Show new tab and content - show(tab, content); - - // Add event details - details.tab = tab; - details.content = content; - - // Emit a custom event - emitEvent(tab, details); - }; - - /** - * Get all of the tabs in a tablist - * @param {Node} tab A tab from the list - * @return {Object} The tabs and the index of the currently active one - */ - var getTabsMap = function (tab) { - var tabGroup = tab.closest('[role="tablist"]'); - var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null; - if (!tabs) return; - return { - tabs: tabs, - index: Array.prototype.indexOf.call(tabs, tab), - }; - }; - - /** - * Switch the active tab based on keyboard activity - * @param {Node} tab The currently active tab - * @param {Key} key The key that was pressed - */ - var switchTabs = function (tab, key) { - // Get a map of tabs - var map = getTabsMap(tab); - if (!map) return; - var length = map.tabs.length - 1; - var index; - - // Go to previous tab - if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) { - index = map.index < 1 ? length : map.index - 1; - } - - // Go to next tab - else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) { - index = map.index === length ? 0 : map.index + 1; - } - - // Go to home - else if (key === "Home") { - index = 0; - } - - // Go to end - else if (key === "End") { - index = length; - } - - // Toggle the tab - toggle(map.tabs[index]); - }; - - /** - * Create the Constructor object - */ - var Constructor = function (selector, options) { - // - // Variables - // - - var publicAPIs = {}; - var settings, tabWrapper; - - // - // Methods - // - - publicAPIs.destroy = function () { - // Get all tabs - var tabs = tabWrapper.querySelectorAll("a"); - - // Add roles to tabs - Array.prototype.forEach.call(tabs, function (tab) { - // Get the tab content - var content = document.querySelector(tab.hash); - if (!content) return; - - // Setup the tab - destroyTab(tab, content, settings); - }); - - // Remove role from wrapper - tabWrapper.removeAttribute("role"); - - // Remove event listeners - document.documentElement.removeEventListener( - "click", - clickHandler, - true - ); - tabWrapper.removeEventListener("keydown", keyHandler, true); - - // Reset variables - settings = null; - tabWrapper = null; - }; - - /** - * Setup the DOM with the proper attributes - */ - publicAPIs.setup = function () { - // Variables - tabWrapper = document.querySelector(selector); - if (!tabWrapper) return; - var tabs = tabWrapper.querySelectorAll("a"); - - // Add role to wrapper - tabWrapper.setAttribute("role", "tablist"); - - // Add roles to tabs. provide dynanmic tab indexes if we are within reveal - var contentTabindexes = - window.document.body.classList.contains("reveal-viewport"); - var nextTabindex = 1; - Array.prototype.forEach.call(tabs, function (tab) { - if (contentTabindexes) { - tab.setAttribute("tabindex", "" + nextTabindex++); - } else { - tab.setAttribute("tabindex", "0"); - } - - // Get the tab content - var content = document.querySelector(tab.hash); - if (!content) return; - - // set tab indexes for content - if (contentTabindexes) { - getKeyboardFocusableElements(content).forEach(function (el) { - el.setAttribute("tabindex", "" + nextTabindex++); - }); - } - - // Setup the tab - setupTab(tab, content, settings); - }); - }; - - /** - * Toggle a tab based on an ID - * @param {String|Node} id The tab to toggle - */ - publicAPIs.toggle = function (id) { - // Get the tab - var tab = id; - if (typeof id === "string") { - tab = document.querySelector( - selector + ' [role="tab"][href*="' + id + '"]' - ); - } - - // Toggle the tab - toggle(tab); - }; - - /** - * Handle click events - */ - var clickHandler = function (event) { - // Only run on toggles - var tab = event.target.closest(selector + ' [role="tab"]'); - if (!tab) return; - - // Prevent link behavior - event.preventDefault(); - - // Toggle the tab - toggle(tab); - }; - - /** - * Handle keydown events - */ - var keyHandler = function (event) { - // Only run if a tab is in focus - var tab = document.activeElement; - if (!tab.matches(selector + ' [role="tab"]')) return; - - // Only run for specific keys - if (["Home", "End"].indexOf(event.key) < 0) return; - - // Switch tabs - switchTabs(tab, event.key); - }; - - /** - * Initialize the instance - */ - var init = function () { - // Merge user options with defaults - settings = extend(defaults, options || {}); - - // Setup the DOM - publicAPIs.setup(); - - // Add event listeners - document.documentElement.addEventListener("click", clickHandler, true); - tabWrapper.addEventListener("keydown", keyHandler, true); - }; - - // - // Initialize and return the Public APIs - // - - init(); - return publicAPIs; - }; - - // - // Return the Constructor - // - - return Constructor; - } -); diff --git a/choosing_files/libs/quarto-html/tippy.css b/choosing_files/libs/quarto-html/tippy.css deleted file mode 100644 index e6ae635..0000000 --- a/choosing_files/libs/quarto-html/tippy.css +++ /dev/null @@ -1 +0,0 @@ -.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/choosing_files/libs/quarto-html/tippy.umd.min.js b/choosing_files/libs/quarto-html/tippy.umd.min.js deleted file mode 100644 index ca292be..0000000 --- a/choosing_files/libs/quarto-html/tippy.umd.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); - diff --git a/choosing_files/libs/revealjs/dist/reset.css b/choosing_files/libs/revealjs/dist/reset.css deleted file mode 100644 index e238539..0000000 --- a/choosing_files/libs/revealjs/dist/reset.css +++ /dev/null @@ -1,30 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v4.0 | 20180602 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -main, menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, main, menu, nav, section { - display: block; -} \ No newline at end of file diff --git a/choosing_files/libs/revealjs/dist/reveal.css b/choosing_files/libs/revealjs/dist/reveal.css deleted file mode 100644 index 5f80fd0..0000000 --- a/choosing_files/libs/revealjs/dist/reveal.css +++ /dev/null @@ -1,8 +0,0 @@ -/*! -* reveal.js 4.3.1 -* https://revealjs.com -* MIT licensed -* -* Copyright (C) 2011-2022 Hakim El Hattab, https://hakim.se -*/ -.reveal .r-stretch,.reveal .stretch{max-width:none;max-height:none}.reveal pre.r-stretch code,.reveal pre.stretch code{height:100%;max-height:100%;box-sizing:border-box}.reveal .r-fit-text{display:inline-block;white-space:nowrap}.reveal .r-stack{display:grid}.reveal .r-stack>*{grid-area:1/1;margin:auto}.reveal .r-hstack,.reveal .r-vstack{display:flex}.reveal .r-hstack img,.reveal .r-hstack video,.reveal .r-vstack img,.reveal .r-vstack video{min-width:0;min-height:0;-o-object-fit:contain;object-fit:contain}.reveal .r-vstack{flex-direction:column;align-items:center;justify-content:center}.reveal .r-hstack{flex-direction:row;align-items:center;justify-content:center}.reveal .items-stretch{align-items:stretch}.reveal .items-start{align-items:flex-start}.reveal .items-center{align-items:center}.reveal .items-end{align-items:flex-end}.reveal .justify-between{justify-content:space-between}.reveal .justify-around{justify-content:space-around}.reveal .justify-start{justify-content:flex-start}.reveal .justify-center{justify-content:center}.reveal .justify-end{justify-content:flex-end}html.reveal-full-page{width:100%;height:100%;height:100vh;height:calc(var(--vh,1vh) * 100);overflow:hidden}.reveal-viewport{height:100%;overflow:hidden;position:relative;line-height:1;margin:0;background-color:#fff;color:#000}.reveal-viewport:-webkit-full-screen{top:0!important;left:0!important;width:100%!important;height:100%!important;transform:none!important}.reveal-viewport:-ms-fullscreen{top:0!important;left:0!important;width:100%!important;height:100%!important;transform:none!important}.reveal-viewport:fullscreen{top:0!important;left:0!important;width:100%!important;height:100%!important;transform:none!important}.reveal .slides section .fragment{opacity:0;visibility:hidden;transition:all .2s ease;will-change:opacity}.reveal .slides section .fragment.visible{opacity:1;visibility:inherit}.reveal .slides section .fragment.disabled{transition:none}.reveal .slides section .fragment.grow{opacity:1;visibility:inherit}.reveal .slides section .fragment.grow.visible{transform:scale(1.3)}.reveal .slides section .fragment.shrink{opacity:1;visibility:inherit}.reveal .slides section .fragment.shrink.visible{transform:scale(.7)}.reveal .slides section .fragment.zoom-in{transform:scale(.1)}.reveal .slides section .fragment.zoom-in.visible{transform:none}.reveal .slides section .fragment.fade-out{opacity:1;visibility:inherit}.reveal .slides section .fragment.fade-out.visible{opacity:0;visibility:hidden}.reveal .slides section .fragment.semi-fade-out{opacity:1;visibility:inherit}.reveal .slides section .fragment.semi-fade-out.visible{opacity:.5;visibility:inherit}.reveal .slides section .fragment.strike{opacity:1;visibility:inherit}.reveal .slides section .fragment.strike.visible{text-decoration:line-through}.reveal .slides section .fragment.fade-up{transform:translate(0,40px)}.reveal .slides section .fragment.fade-up.visible{transform:translate(0,0)}.reveal .slides section .fragment.fade-down{transform:translate(0,-40px)}.reveal .slides section .fragment.fade-down.visible{transform:translate(0,0)}.reveal .slides section .fragment.fade-right{transform:translate(-40px,0)}.reveal .slides section .fragment.fade-right.visible{transform:translate(0,0)}.reveal .slides section .fragment.fade-left{transform:translate(40px,0)}.reveal .slides section .fragment.fade-left.visible{transform:translate(0,0)}.reveal .slides section .fragment.current-visible,.reveal .slides section .fragment.fade-in-then-out{opacity:0;visibility:hidden}.reveal .slides section .fragment.current-visible.current-fragment,.reveal .slides section .fragment.fade-in-then-out.current-fragment{opacity:1;visibility:inherit}.reveal .slides section .fragment.fade-in-then-semi-out{opacity:0;visibility:hidden}.reveal .slides section .fragment.fade-in-then-semi-out.visible{opacity:.5;visibility:inherit}.reveal .slides section .fragment.fade-in-then-semi-out.current-fragment{opacity:1;visibility:inherit}.reveal .slides section .fragment.highlight-blue,.reveal .slides section .fragment.highlight-current-blue,.reveal .slides section .fragment.highlight-current-green,.reveal .slides section .fragment.highlight-current-red,.reveal .slides section .fragment.highlight-green,.reveal .slides section .fragment.highlight-red{opacity:1;visibility:inherit}.reveal .slides section .fragment.highlight-red.visible{color:#ff2c2d}.reveal .slides section .fragment.highlight-green.visible{color:#17ff2e}.reveal .slides section .fragment.highlight-blue.visible{color:#1b91ff}.reveal .slides section .fragment.highlight-current-red.current-fragment{color:#ff2c2d}.reveal .slides section .fragment.highlight-current-green.current-fragment{color:#17ff2e}.reveal .slides section .fragment.highlight-current-blue.current-fragment{color:#1b91ff}.reveal:after{content:"";font-style:italic}.reveal iframe{z-index:1}.reveal a{position:relative}@keyframes bounce-right{0%,10%,25%,40%,50%{transform:translateX(0)}20%{transform:translateX(10px)}30%{transform:translateX(-5px)}}@keyframes bounce-left{0%,10%,25%,40%,50%{transform:translateX(0)}20%{transform:translateX(-10px)}30%{transform:translateX(5px)}}@keyframes bounce-down{0%,10%,25%,40%,50%{transform:translateY(0)}20%{transform:translateY(10px)}30%{transform:translateY(-5px)}}.reveal .controls{display:none;position:absolute;top:auto;bottom:12px;right:12px;left:auto;z-index:11;color:#000;pointer-events:none;font-size:10px}.reveal .controls button{position:absolute;padding:0;background-color:transparent;border:0;outline:0;cursor:pointer;color:currentColor;transform:scale(.9999);transition:color .2s ease,opacity .2s ease,transform .2s ease;z-index:2;pointer-events:auto;font-size:inherit;visibility:hidden;opacity:0;-webkit-appearance:none;-webkit-tap-highlight-color:transparent}.reveal .controls .controls-arrow:after,.reveal .controls .controls-arrow:before{content:"";position:absolute;top:0;left:0;width:2.6em;height:.5em;border-radius:.25em;background-color:currentColor;transition:all .15s ease,background-color .8s ease;transform-origin:.2em 50%;will-change:transform}.reveal .controls .controls-arrow{position:relative;width:3.6em;height:3.6em}.reveal .controls .controls-arrow:before{transform:translateX(.5em) translateY(1.55em) rotate(45deg)}.reveal .controls .controls-arrow:after{transform:translateX(.5em) translateY(1.55em) rotate(-45deg)}.reveal .controls .controls-arrow:hover:before{transform:translateX(.5em) translateY(1.55em) rotate(40deg)}.reveal .controls .controls-arrow:hover:after{transform:translateX(.5em) translateY(1.55em) rotate(-40deg)}.reveal .controls .controls-arrow:active:before{transform:translateX(.5em) translateY(1.55em) rotate(36deg)}.reveal .controls .controls-arrow:active:after{transform:translateX(.5em) translateY(1.55em) rotate(-36deg)}.reveal .controls .navigate-left{right:6.4em;bottom:3.2em;transform:translateX(-10px)}.reveal .controls .navigate-left.highlight{animation:bounce-left 2s 50 both ease-out}.reveal .controls .navigate-right{right:0;bottom:3.2em;transform:translateX(10px)}.reveal .controls .navigate-right .controls-arrow{transform:rotate(180deg)}.reveal .controls .navigate-right.highlight{animation:bounce-right 2s 50 both ease-out}.reveal .controls .navigate-up{right:3.2em;bottom:6.4em;transform:translateY(-10px)}.reveal .controls .navigate-up .controls-arrow{transform:rotate(90deg)}.reveal .controls .navigate-down{right:3.2em;bottom:-1.4em;padding-bottom:1.4em;transform:translateY(10px)}.reveal .controls .navigate-down .controls-arrow{transform:rotate(-90deg)}.reveal .controls .navigate-down.highlight{animation:bounce-down 2s 50 both ease-out}.reveal .controls[data-controls-back-arrows=faded] .navigate-up.enabled{opacity:.3}.reveal .controls[data-controls-back-arrows=faded] .navigate-up.enabled:hover{opacity:1}.reveal .controls[data-controls-back-arrows=hidden] .navigate-up.enabled{opacity:0;visibility:hidden}.reveal .controls .enabled{visibility:visible;opacity:.9;cursor:pointer;transform:none}.reveal .controls .enabled.fragmented{opacity:.5}.reveal .controls .enabled.fragmented:hover,.reveal .controls .enabled:hover{opacity:1}.reveal:not(.rtl) .controls[data-controls-back-arrows=faded] .navigate-left.enabled{opacity:.3}.reveal:not(.rtl) .controls[data-controls-back-arrows=faded] .navigate-left.enabled:hover{opacity:1}.reveal:not(.rtl) .controls[data-controls-back-arrows=hidden] .navigate-left.enabled{opacity:0;visibility:hidden}.reveal.rtl .controls[data-controls-back-arrows=faded] .navigate-right.enabled{opacity:.3}.reveal.rtl .controls[data-controls-back-arrows=faded] .navigate-right.enabled:hover{opacity:1}.reveal.rtl .controls[data-controls-back-arrows=hidden] .navigate-right.enabled{opacity:0;visibility:hidden}.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-down,.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-up{display:none}.reveal:not(.has-vertical-slides) .controls .navigate-left,.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-left{bottom:1.4em;right:5.5em}.reveal:not(.has-vertical-slides) .controls .navigate-right,.reveal[data-navigation-mode=linear].has-horizontal-slides .navigate-right{bottom:1.4em;right:.5em}.reveal:not(.has-horizontal-slides) .controls .navigate-up{right:1.4em;bottom:5em}.reveal:not(.has-horizontal-slides) .controls .navigate-down{right:1.4em;bottom:.5em}.reveal.has-dark-background .controls{color:#fff}.reveal.has-light-background .controls{color:#000}.reveal.no-hover .controls .controls-arrow:active:before,.reveal.no-hover .controls .controls-arrow:hover:before{transform:translateX(.5em) translateY(1.55em) rotate(45deg)}.reveal.no-hover .controls .controls-arrow:active:after,.reveal.no-hover .controls .controls-arrow:hover:after{transform:translateX(.5em) translateY(1.55em) rotate(-45deg)}@media screen and (min-width:500px){.reveal .controls[data-controls-layout=edges]{top:0;right:0;bottom:0;left:0}.reveal .controls[data-controls-layout=edges] .navigate-down,.reveal .controls[data-controls-layout=edges] .navigate-left,.reveal .controls[data-controls-layout=edges] .navigate-right,.reveal .controls[data-controls-layout=edges] .navigate-up{bottom:auto;right:auto}.reveal .controls[data-controls-layout=edges] .navigate-left{top:50%;left:.8em;margin-top:-1.8em}.reveal .controls[data-controls-layout=edges] .navigate-right{top:50%;right:.8em;margin-top:-1.8em}.reveal .controls[data-controls-layout=edges] .navigate-up{top:.8em;left:50%;margin-left:-1.8em}.reveal .controls[data-controls-layout=edges] .navigate-down{bottom:-.3em;left:50%;margin-left:-1.8em}}.reveal .progress{position:absolute;display:none;height:3px;width:100%;bottom:0;left:0;z-index:10;background-color:rgba(0,0,0,.2);color:#fff}.reveal .progress:after{content:"";display:block;position:absolute;height:10px;width:100%;top:-10px}.reveal .progress span{display:block;height:100%;width:100%;background-color:currentColor;transition:transform .8s cubic-bezier(.26,.86,.44,.985);transform-origin:0 0;transform:scaleX(0)}.reveal .slide-number{position:absolute;display:block;right:8px;bottom:8px;z-index:31;font-family:Helvetica,sans-serif;font-size:12px;line-height:1;color:#fff;background-color:rgba(0,0,0,.4);padding:5px}.reveal .slide-number a{color:currentColor}.reveal .slide-number-delimiter{margin:0 3px}.reveal{position:relative;width:100%;height:100%;overflow:hidden;touch-action:pinch-zoom}.reveal.embedded{touch-action:pan-y}.reveal .slides{position:absolute;width:100%;height:100%;top:0;right:0;bottom:0;left:0;margin:auto;pointer-events:none;overflow:visible;z-index:1;text-align:center;perspective:600px;perspective-origin:50% 40%}.reveal .slides>section{perspective:600px}.reveal .slides>section,.reveal .slides>section>section{display:none;position:absolute;width:100%;pointer-events:auto;z-index:10;transform-style:flat;transition:transform-origin .8s cubic-bezier(.26,.86,.44,.985),transform .8s cubic-bezier(.26,.86,.44,.985),visibility .8s cubic-bezier(.26,.86,.44,.985),opacity .8s cubic-bezier(.26,.86,.44,.985)}.reveal[data-transition-speed=fast] .slides section{transition-duration:.4s}.reveal[data-transition-speed=slow] .slides section{transition-duration:1.2s}.reveal .slides section[data-transition-speed=fast]{transition-duration:.4s}.reveal .slides section[data-transition-speed=slow]{transition-duration:1.2s}.reveal .slides>section.stack{padding-top:0;padding-bottom:0;pointer-events:none;height:100%}.reveal .slides>section.present,.reveal .slides>section>section.present{display:block;z-index:11;opacity:1}.reveal .slides>section:empty,.reveal .slides>section>section:empty,.reveal .slides>section>section[data-background-interactive],.reveal .slides>section[data-background-interactive]{pointer-events:none}.reveal.center,.reveal.center .slides,.reveal.center .slides section{min-height:0!important}.reveal .slides>section:not(.present),.reveal .slides>section>section:not(.present){pointer-events:none}.reveal.overview .slides>section,.reveal.overview .slides>section>section{pointer-events:auto}.reveal .slides>section.future,.reveal .slides>section.past,.reveal .slides>section>section.future,.reveal .slides>section>section.past{opacity:0}.reveal .slides>section[data-transition=slide].past,.reveal .slides>section[data-transition~=slide-out].past,.reveal.slide .slides>section:not([data-transition]).past{transform:translate(-150%,0)}.reveal .slides>section[data-transition=slide].future,.reveal .slides>section[data-transition~=slide-in].future,.reveal.slide .slides>section:not([data-transition]).future{transform:translate(150%,0)}.reveal .slides>section>section[data-transition=slide].past,.reveal .slides>section>section[data-transition~=slide-out].past,.reveal.slide .slides>section>section:not([data-transition]).past{transform:translate(0,-150%)}.reveal .slides>section>section[data-transition=slide].future,.reveal .slides>section>section[data-transition~=slide-in].future,.reveal.slide .slides>section>section:not([data-transition]).future{transform:translate(0,150%)}.reveal .slides>section[data-transition=linear].past,.reveal .slides>section[data-transition~=linear-out].past,.reveal.linear .slides>section:not([data-transition]).past{transform:translate(-150%,0)}.reveal .slides>section[data-transition=linear].future,.reveal .slides>section[data-transition~=linear-in].future,.reveal.linear .slides>section:not([data-transition]).future{transform:translate(150%,0)}.reveal .slides>section>section[data-transition=linear].past,.reveal .slides>section>section[data-transition~=linear-out].past,.reveal.linear .slides>section>section:not([data-transition]).past{transform:translate(0,-150%)}.reveal .slides>section>section[data-transition=linear].future,.reveal .slides>section>section[data-transition~=linear-in].future,.reveal.linear .slides>section>section:not([data-transition]).future{transform:translate(0,150%)}.reveal .slides section[data-transition=default].stack,.reveal.default .slides section.stack{transform-style:preserve-3d}.reveal .slides>section[data-transition=default].past,.reveal .slides>section[data-transition~=default-out].past,.reveal.default .slides>section:not([data-transition]).past{transform:translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0)}.reveal .slides>section[data-transition=default].future,.reveal .slides>section[data-transition~=default-in].future,.reveal.default .slides>section:not([data-transition]).future{transform:translate3d(100%,0,0) rotateY(90deg) translate3d(100%,0,0)}.reveal .slides>section>section[data-transition=default].past,.reveal .slides>section>section[data-transition~=default-out].past,.reveal.default .slides>section>section:not([data-transition]).past{transform:translate3d(0,-300px,0) rotateX(70deg) translate3d(0,-300px,0)}.reveal .slides>section>section[data-transition=default].future,.reveal .slides>section>section[data-transition~=default-in].future,.reveal.default .slides>section>section:not([data-transition]).future{transform:translate3d(0,300px,0) rotateX(-70deg) translate3d(0,300px,0)}.reveal .slides section[data-transition=convex].stack,.reveal.convex .slides section.stack{transform-style:preserve-3d}.reveal .slides>section[data-transition=convex].past,.reveal .slides>section[data-transition~=convex-out].past,.reveal.convex .slides>section:not([data-transition]).past{transform:translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0)}.reveal .slides>section[data-transition=convex].future,.reveal .slides>section[data-transition~=convex-in].future,.reveal.convex .slides>section:not([data-transition]).future{transform:translate3d(100%,0,0) rotateY(90deg) translate3d(100%,0,0)}.reveal .slides>section>section[data-transition=convex].past,.reveal .slides>section>section[data-transition~=convex-out].past,.reveal.convex .slides>section>section:not([data-transition]).past{transform:translate3d(0,-300px,0) rotateX(70deg) translate3d(0,-300px,0)}.reveal .slides>section>section[data-transition=convex].future,.reveal .slides>section>section[data-transition~=convex-in].future,.reveal.convex .slides>section>section:not([data-transition]).future{transform:translate3d(0,300px,0) rotateX(-70deg) translate3d(0,300px,0)}.reveal .slides section[data-transition=concave].stack,.reveal.concave .slides section.stack{transform-style:preserve-3d}.reveal .slides>section[data-transition=concave].past,.reveal .slides>section[data-transition~=concave-out].past,.reveal.concave .slides>section:not([data-transition]).past{transform:translate3d(-100%,0,0) rotateY(90deg) translate3d(-100%,0,0)}.reveal .slides>section[data-transition=concave].future,.reveal .slides>section[data-transition~=concave-in].future,.reveal.concave .slides>section:not([data-transition]).future{transform:translate3d(100%,0,0) rotateY(-90deg) translate3d(100%,0,0)}.reveal .slides>section>section[data-transition=concave].past,.reveal .slides>section>section[data-transition~=concave-out].past,.reveal.concave .slides>section>section:not([data-transition]).past{transform:translate3d(0,-80%,0) rotateX(-70deg) translate3d(0,-80%,0)}.reveal .slides>section>section[data-transition=concave].future,.reveal .slides>section>section[data-transition~=concave-in].future,.reveal.concave .slides>section>section:not([data-transition]).future{transform:translate3d(0,80%,0) rotateX(70deg) translate3d(0,80%,0)}.reveal .slides section[data-transition=zoom],.reveal.zoom .slides section:not([data-transition]){transition-timing-function:ease}.reveal .slides>section[data-transition=zoom].past,.reveal .slides>section[data-transition~=zoom-out].past,.reveal.zoom .slides>section:not([data-transition]).past{visibility:hidden;transform:scale(16)}.reveal .slides>section[data-transition=zoom].future,.reveal .slides>section[data-transition~=zoom-in].future,.reveal.zoom .slides>section:not([data-transition]).future{visibility:hidden;transform:scale(.2)}.reveal .slides>section>section[data-transition=zoom].past,.reveal .slides>section>section[data-transition~=zoom-out].past,.reveal.zoom .slides>section>section:not([data-transition]).past{transform:scale(16)}.reveal .slides>section>section[data-transition=zoom].future,.reveal .slides>section>section[data-transition~=zoom-in].future,.reveal.zoom .slides>section>section:not([data-transition]).future{transform:scale(.2)}.reveal.cube .slides{perspective:1300px}.reveal.cube .slides section{padding:30px;min-height:700px;-webkit-backface-visibility:hidden;backface-visibility:hidden;box-sizing:border-box;transform-style:preserve-3d}.reveal.center.cube .slides section{min-height:0}.reveal.cube .slides section:not(.stack):before{content:"";position:absolute;display:block;width:100%;height:100%;left:0;top:0;background:rgba(0,0,0,.1);border-radius:4px;transform:translateZ(-20px)}.reveal.cube .slides section:not(.stack):after{content:"";position:absolute;display:block;width:90%;height:30px;left:5%;bottom:0;background:0 0;z-index:1;border-radius:4px;box-shadow:0 95px 25px rgba(0,0,0,.2);transform:translateZ(-90px) rotateX(65deg)}.reveal.cube .slides>section.stack{padding:0;background:0 0}.reveal.cube .slides>section.past{transform-origin:100% 0;transform:translate3d(-100%,0,0) rotateY(-90deg)}.reveal.cube .slides>section.future{transform-origin:0 0;transform:translate3d(100%,0,0) rotateY(90deg)}.reveal.cube .slides>section>section.past{transform-origin:0 100%;transform:translate3d(0,-100%,0) rotateX(90deg)}.reveal.cube .slides>section>section.future{transform-origin:0 0;transform:translate3d(0,100%,0) rotateX(-90deg)}.reveal.page .slides{perspective-origin:0 50%;perspective:3000px}.reveal.page .slides section{padding:30px;min-height:700px;box-sizing:border-box;transform-style:preserve-3d}.reveal.page .slides section.past{z-index:12}.reveal.page .slides section:not(.stack):before{content:"";position:absolute;display:block;width:100%;height:100%;left:0;top:0;background:rgba(0,0,0,.1);transform:translateZ(-20px)}.reveal.page .slides section:not(.stack):after{content:"";position:absolute;display:block;width:90%;height:30px;left:5%;bottom:0;background:0 0;z-index:1;border-radius:4px;box-shadow:0 95px 25px rgba(0,0,0,.2);-webkit-transform:translateZ(-90px) rotateX(65deg)}.reveal.page .slides>section.stack{padding:0;background:0 0}.reveal.page .slides>section.past{transform-origin:0 0;transform:translate3d(-40%,0,0) rotateY(-80deg)}.reveal.page .slides>section.future{transform-origin:100% 0;transform:translate3d(0,0,0)}.reveal.page .slides>section>section.past{transform-origin:0 0;transform:translate3d(0,-40%,0) rotateX(80deg)}.reveal.page .slides>section>section.future{transform-origin:0 100%;transform:translate3d(0,0,0)}.reveal .slides section[data-transition=fade],.reveal.fade .slides section:not([data-transition]),.reveal.fade .slides>section>section:not([data-transition]){transform:none;transition:opacity .5s}.reveal.fade.overview .slides section,.reveal.fade.overview .slides>section>section{transition:none}.reveal .slides section[data-transition=none],.reveal.none .slides section:not([data-transition]){transform:none;transition:none}.reveal .pause-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#000;visibility:hidden;opacity:0;z-index:100;transition:all 1s ease}.reveal .pause-overlay .resume-button{position:absolute;bottom:20px;right:20px;color:#ccc;border-radius:2px;padding:6px 14px;border:2px solid #ccc;font-size:16px;background:0 0;cursor:pointer}.reveal .pause-overlay .resume-button:hover{color:#fff;border-color:#fff}.reveal.paused .pause-overlay{visibility:visible;opacity:1}.reveal .no-transition,.reveal .no-transition *,.reveal .slides.disable-slide-transitions section{transition:none!important}.reveal .slides.disable-slide-transitions section{transform:none!important}.reveal .backgrounds{position:absolute;width:100%;height:100%;top:0;left:0;perspective:600px}.reveal .slide-background{display:none;position:absolute;width:100%;height:100%;opacity:0;visibility:hidden;overflow:hidden;background-color:rgba(0,0,0,0);transition:all .8s cubic-bezier(.26,.86,.44,.985)}.reveal .slide-background-content{position:absolute;width:100%;height:100%;background-position:50% 50%;background-repeat:no-repeat;background-size:cover}.reveal .slide-background.stack{display:block}.reveal .slide-background.present{opacity:1;visibility:visible;z-index:2}.print-pdf .reveal .slide-background{opacity:1!important;visibility:visible!important}.reveal .slide-background video{position:absolute;width:100%;height:100%;max-width:none;max-height:none;top:0;left:0;-o-object-fit:cover;object-fit:cover}.reveal .slide-background[data-background-size=contain] video{-o-object-fit:contain;object-fit:contain}.reveal>.backgrounds .slide-background[data-background-transition=none],.reveal[data-background-transition=none]>.backgrounds .slide-background:not([data-background-transition]){transition:none}.reveal>.backgrounds .slide-background[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background:not([data-background-transition]){opacity:1}.reveal>.backgrounds .slide-background.past[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background.past:not([data-background-transition]){transform:translate(-100%,0)}.reveal>.backgrounds .slide-background.future[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background.future:not([data-background-transition]){transform:translate(100%,0)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]){transform:translate(0,-100%)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=slide],.reveal[data-background-transition=slide]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]){transform:translate(0,100%)}.reveal>.backgrounds .slide-background.past[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background.past:not([data-background-transition]){opacity:0;transform:translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0)}.reveal>.backgrounds .slide-background.future[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background.future:not([data-background-transition]){opacity:0;transform:translate3d(100%,0,0) rotateY(90deg) translate3d(100%,0,0)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]){opacity:0;transform:translate3d(0,-100%,0) rotateX(90deg) translate3d(0,-100%,0)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=convex],.reveal[data-background-transition=convex]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]){opacity:0;transform:translate3d(0,100%,0) rotateX(-90deg) translate3d(0,100%,0)}.reveal>.backgrounds .slide-background.past[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background.past:not([data-background-transition]){opacity:0;transform:translate3d(-100%,0,0) rotateY(90deg) translate3d(-100%,0,0)}.reveal>.backgrounds .slide-background.future[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background.future:not([data-background-transition]){opacity:0;transform:translate3d(100%,0,0) rotateY(-90deg) translate3d(100%,0,0)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]){opacity:0;transform:translate3d(0,-100%,0) rotateX(-90deg) translate3d(0,-100%,0)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=concave],.reveal[data-background-transition=concave]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]){opacity:0;transform:translate3d(0,100%,0) rotateX(90deg) translate3d(0,100%,0)}.reveal>.backgrounds .slide-background[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background:not([data-background-transition]){transition-timing-function:ease}.reveal>.backgrounds .slide-background.past[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background.past:not([data-background-transition]){opacity:0;visibility:hidden;transform:scale(16)}.reveal>.backgrounds .slide-background.future[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background.future:not([data-background-transition]){opacity:0;visibility:hidden;transform:scale(.2)}.reveal>.backgrounds .slide-background>.slide-background.past[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background>.slide-background.past:not([data-background-transition]){opacity:0;visibility:hidden;transform:scale(16)}.reveal>.backgrounds .slide-background>.slide-background.future[data-background-transition=zoom],.reveal[data-background-transition=zoom]>.backgrounds .slide-background>.slide-background.future:not([data-background-transition]){opacity:0;visibility:hidden;transform:scale(.2)}.reveal[data-transition-speed=fast]>.backgrounds .slide-background{transition-duration:.4s}.reveal[data-transition-speed=slow]>.backgrounds .slide-background{transition-duration:1.2s}.reveal [data-auto-animate-target^=unmatched]{will-change:opacity}.reveal section[data-auto-animate]:not(.stack):not([data-auto-animate=running]) [data-auto-animate-target^=unmatched]{opacity:0}.reveal.overview{perspective-origin:50% 50%;perspective:700px}.reveal.overview .slides{-moz-transform-style:preserve-3d}.reveal.overview .slides section{height:100%;top:0!important;opacity:1!important;overflow:hidden;visibility:visible!important;cursor:pointer;box-sizing:border-box}.reveal.overview .slides section.present,.reveal.overview .slides section:hover{outline:10px solid rgba(150,150,150,.4);outline-offset:10px}.reveal.overview .slides section .fragment{opacity:1;transition:none}.reveal.overview .slides section:after,.reveal.overview .slides section:before{display:none!important}.reveal.overview .slides>section.stack{padding:0;top:0!important;background:0 0;outline:0;overflow:visible}.reveal.overview .backgrounds{perspective:inherit;-moz-transform-style:preserve-3d}.reveal.overview .backgrounds .slide-background{opacity:1;visibility:visible;outline:10px solid rgba(150,150,150,.1);outline-offset:10px}.reveal.overview .backgrounds .slide-background.stack{overflow:visible}.reveal.overview .slides section,.reveal.overview-deactivating .slides section{transition:none}.reveal.overview .backgrounds .slide-background,.reveal.overview-deactivating .backgrounds .slide-background{transition:none}.reveal.rtl .slides,.reveal.rtl .slides h1,.reveal.rtl .slides h2,.reveal.rtl .slides h3,.reveal.rtl .slides h4,.reveal.rtl .slides h5,.reveal.rtl .slides h6{direction:rtl;font-family:sans-serif}.reveal.rtl code,.reveal.rtl pre{direction:ltr}.reveal.rtl ol,.reveal.rtl ul{text-align:right}.reveal.rtl .progress span{transform-origin:100% 0}.reveal.has-parallax-background .backgrounds{transition:all .8s ease}.reveal.has-parallax-background[data-transition-speed=fast] .backgrounds{transition-duration:.4s}.reveal.has-parallax-background[data-transition-speed=slow] .backgrounds{transition-duration:1.2s}.reveal>.overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1000;background:rgba(0,0,0,.9);transition:all .3s ease}.reveal>.overlay .spinner{position:absolute;display:block;top:50%;left:50%;width:32px;height:32px;margin:-16px 0 0 -16px;z-index:10;background-image:url(data:image/gif;base64,R0lGODlhIAAgAPMAAJmZmf%2F%2F%2F6%2Bvr8nJybW1tcDAwOjo6Nvb26ioqKOjo7Ozs%2FLy8vz8%2FAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ%2FV%2FnmOM82XiHRLYKhKP1oZmADdEAAAh%2BQQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY%2FCZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB%2BA4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6%2BHo7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq%2BB6QDtuetcaBPnW6%2BO7wDHpIiK9SaVK5GgV543tzjgGcghAgAh%2BQQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK%2B%2BG%2Bw48edZPK%2BM6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE%2BG%2BcD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm%2BFNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk%2BaV%2BoJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0%2FVNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc%2BXiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30%2FiI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE%2FjiuL04RGEBgwWhShRgQExHBAAh%2BQQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR%2BipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq%2BE71SRQeyqUToLA7VxF0JDyIQh%2FMVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY%2BYip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd%2BMFCN6HAAIKgNggY0KtEBAAh%2BQQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1%2BvsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d%2BjYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg%2BygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0%2Bbm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h%2BKr0SJ8MFihpNbx%2B4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX%2BBP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA%3D%3D);visibility:visible;opacity:.6;transition:all .3s ease}.reveal>.overlay header{position:absolute;left:0;top:0;width:100%;padding:5px;z-index:2;box-sizing:border-box}.reveal>.overlay header a{display:inline-block;width:40px;height:40px;line-height:36px;padding:0 10px;float:right;opacity:.6;box-sizing:border-box}.reveal>.overlay header a:hover{opacity:1}.reveal>.overlay header a .icon{display:inline-block;width:20px;height:20px;background-position:50% 50%;background-size:100%;background-repeat:no-repeat}.reveal>.overlay header a.close .icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABkklEQVRYR8WX4VHDMAxG6wnoJrABZQPYBCaBTWAD2g1gE5gg6OOsXuxIlr40d81dfrSJ9V4c2VLK7spHuTJ/5wpM07QXuXc5X0opX2tEJcadjHuV80li/FgxTIEK/5QBCICBD6xEhSMGHgQPgBgLiYVAB1dpSqKDawxTohFw4JSEA3clzgIBPCURwE2JucBR7rhPJJv5OpJwDX+SfDjgx1wACQeJG1aChP9K/IMmdZ8DtESV1WyP3Bt4MwM6sj4NMxMYiqUWHQu4KYA/SYkIjOsm3BXYWMKFDwU2khjCQ4ELJUJ4SmClRArOCmSXGuKma0fYD5CbzHxFpCSGAhfAVSSUGDUk2BWZaff2g6GE15BsBQ9nwmpIGDiyHQddwNTMKkbZaf9fajXQca1EX44puJZUsnY0ObGmITE3GVLCbEhQUjGVt146j6oasWN+49Vph2w1pZ5EansNZqKBm1txbU57iRRcZ86RWMDdWtBJUHBHwoQPi1GV+JCbntmvok7iTX4/Up9mgyTc/FJYDTcndgH/AA5A/CHsyEkVAAAAAElFTkSuQmCC)}.reveal>.overlay header a.external .icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAcElEQVRYR+2WSQoAIQwEzf8f7XiOMkUQxUPlGkM3hVmiQfQR9GYnH1SsAQlI4DiBqkCMoNb9y2e90IAEJPAcgdznU9+engMaeJ7Azh5Y1U67gAho4DqBqmB1buAf0MB1AlVBek83ZPkmJMGc1wAR+AAqod/B97TRpQAAAABJRU5ErkJggg==)}.reveal>.overlay .viewport{position:absolute;display:flex;top:50px;right:0;bottom:0;left:0}.reveal>.overlay.overlay-preview .viewport iframe{width:100%;height:100%;max-width:100%;max-height:100%;border:0;opacity:0;visibility:hidden;transition:all .3s ease}.reveal>.overlay.overlay-preview.loaded .viewport iframe{opacity:1;visibility:visible}.reveal>.overlay.overlay-preview.loaded .viewport-inner{position:absolute;z-index:-1;left:0;top:45%;width:100%;text-align:center;letter-spacing:normal}.reveal>.overlay.overlay-preview .x-frame-error{opacity:0;transition:opacity .3s ease .3s}.reveal>.overlay.overlay-preview.loaded .x-frame-error{opacity:1}.reveal>.overlay.overlay-preview.loaded .spinner{opacity:0;visibility:hidden;transform:scale(.2)}.reveal>.overlay.overlay-help .viewport{overflow:auto;color:#fff}.reveal>.overlay.overlay-help .viewport .viewport-inner{width:600px;margin:auto;padding:20px 20px 80px 20px;text-align:center;letter-spacing:normal}.reveal>.overlay.overlay-help .viewport .viewport-inner .title{font-size:20px}.reveal>.overlay.overlay-help .viewport .viewport-inner table{border:1px solid #fff;border-collapse:collapse;font-size:16px}.reveal>.overlay.overlay-help .viewport .viewport-inner table td,.reveal>.overlay.overlay-help .viewport .viewport-inner table th{width:200px;padding:14px;border:1px solid #fff;vertical-align:middle}.reveal>.overlay.overlay-help .viewport .viewport-inner table th{padding-top:20px;padding-bottom:20px}.reveal .playback{position:absolute;left:15px;bottom:20px;z-index:30;cursor:pointer;transition:all .4s ease;-webkit-tap-highlight-color:transparent}.reveal.overview .playback{opacity:0;visibility:hidden}.reveal .hljs{min-height:100%}.reveal .hljs table{margin:initial}.reveal .hljs-ln-code,.reveal .hljs-ln-numbers{padding:0;border:0}.reveal .hljs-ln-numbers{opacity:.6;padding-right:.75em;text-align:right;vertical-align:top}.reveal .hljs.has-highlights tr:not(.highlight-line){opacity:.4}.reveal .hljs:not(:first-child).fragment{position:absolute;top:0;left:0;width:100%;box-sizing:border-box}.reveal pre[data-auto-animate-target]{overflow:hidden}.reveal pre[data-auto-animate-target] code{height:100%}.reveal .roll{display:inline-block;line-height:1.2;overflow:hidden;vertical-align:top;perspective:400px;perspective-origin:50% 50%}.reveal .roll:hover{background:0 0;text-shadow:none}.reveal .roll span{display:block;position:relative;padding:0 2px;pointer-events:none;transition:all .4s ease;transform-origin:50% 0;transform-style:preserve-3d;-webkit-backface-visibility:hidden;backface-visibility:hidden}.reveal .roll:hover span{background:rgba(0,0,0,.5);transform:translate3d(0,0,-45px) rotateX(90deg)}.reveal .roll span:after{content:attr(data-title);display:block;position:absolute;left:0;top:0;padding:0 2px;-webkit-backface-visibility:hidden;backface-visibility:hidden;transform-origin:50% 0;transform:translate3d(0,110%,0) rotateX(-90deg)}.reveal aside.notes{display:none}.reveal .speaker-notes{display:none;position:absolute;width:33.3333333333%;height:100%;top:0;left:100%;padding:14px 18px 14px 18px;z-index:1;font-size:18px;line-height:1.4;border:1px solid rgba(0,0,0,.05);color:#222;background-color:#f5f5f5;overflow:auto;box-sizing:border-box;text-align:left;font-family:Helvetica,sans-serif;-webkit-overflow-scrolling:touch}.reveal .speaker-notes .notes-placeholder{color:#ccc;font-style:italic}.reveal .speaker-notes:focus{outline:0}.reveal .speaker-notes:before{content:"Speaker notes";display:block;margin-bottom:10px;opacity:.5}.reveal.show-notes{max-width:75%;overflow:visible}.reveal.show-notes .speaker-notes{display:block}@media screen and (min-width:1600px){.reveal .speaker-notes{font-size:20px}}@media screen and (max-width:1024px){.reveal.show-notes{border-left:0;max-width:none;max-height:70%;max-height:70vh;overflow:visible}.reveal.show-notes .speaker-notes{top:100%;left:0;width:100%;height:30vh;border:0}}@media screen and (max-width:600px){.reveal.show-notes{max-height:60%;max-height:60vh}.reveal.show-notes .speaker-notes{top:100%;height:40vh}.reveal .speaker-notes{font-size:14px}}.zoomed .reveal *,.zoomed .reveal :after,.zoomed .reveal :before{-webkit-backface-visibility:visible!important;backface-visibility:visible!important}.zoomed .reveal .controls,.zoomed .reveal .progress{opacity:0}.zoomed .reveal .roll span{background:0 0}.zoomed .reveal .roll span:after{visibility:hidden}html.print-pdf *{-webkit-print-color-adjust:exact}html.print-pdf{width:100%;height:100%;overflow:visible}html.print-pdf body{margin:0 auto!important;border:0;padding:0;float:none!important;overflow:visible}html.print-pdf .nestedarrow,html.print-pdf .reveal .controls,html.print-pdf .reveal .playback,html.print-pdf .reveal .progress,html.print-pdf .reveal.overview,html.print-pdf .state-background{display:none!important}html.print-pdf .reveal pre code{overflow:hidden!important;font-family:Courier,"Courier New",monospace!important}html.print-pdf .reveal{width:auto!important;height:auto!important;overflow:hidden!important}html.print-pdf .reveal .slides{position:static;width:100%!important;height:auto!important;zoom:1!important;pointer-events:initial;left:auto;top:auto;margin:0!important;padding:0!important;overflow:visible;display:block;perspective:none;perspective-origin:50% 50%}html.print-pdf .reveal .slides .pdf-page{position:relative;overflow:hidden;z-index:1;page-break-after:always}html.print-pdf .reveal .slides section{visibility:visible!important;display:block!important;position:absolute!important;margin:0!important;padding:0!important;box-sizing:border-box!important;min-height:1px;opacity:1!important;transform-style:flat!important;transform:none!important}html.print-pdf .reveal section.stack{position:relative!important;margin:0!important;padding:0!important;page-break-after:avoid!important;height:auto!important;min-height:auto!important}html.print-pdf .reveal img{box-shadow:none}html.print-pdf .reveal .backgrounds{display:none}html.print-pdf .reveal .slide-background{display:block!important;position:absolute;top:0;left:0;width:100%;height:100%;z-index:auto!important}html.print-pdf .reveal.show-notes{max-width:none;max-height:none}html.print-pdf .reveal .speaker-notes-pdf{display:block;width:100%;height:auto;max-height:none;top:auto;right:auto;bottom:auto;left:auto;z-index:100}html.print-pdf .reveal .speaker-notes-pdf[data-layout=separate-page]{position:relative;color:inherit;background-color:transparent;padding:20px;page-break-after:always;border:0}html.print-pdf .reveal .slide-number-pdf{display:block;position:absolute;font-size:14px}html.print-pdf .aria-status{display:none}@media print{html:not(.print-pdf){background:#fff;width:auto;height:auto;overflow:visible}html:not(.print-pdf) body{background:#fff;font-size:20pt;width:auto;height:auto;border:0;margin:0 5%;padding:0;overflow:visible;float:none!important}html:not(.print-pdf) .controls,html:not(.print-pdf) .fork-reveal,html:not(.print-pdf) .nestedarrow,html:not(.print-pdf) .reveal .backgrounds,html:not(.print-pdf) .reveal .progress,html:not(.print-pdf) .reveal .slide-number,html:not(.print-pdf) .share-reveal,html:not(.print-pdf) .state-background{display:none!important}html:not(.print-pdf) body,html:not(.print-pdf) li,html:not(.print-pdf) p,html:not(.print-pdf) td{font-size:20pt!important;color:#000}html:not(.print-pdf) h1,html:not(.print-pdf) h2,html:not(.print-pdf) h3,html:not(.print-pdf) h4,html:not(.print-pdf) h5,html:not(.print-pdf) h6{color:#000!important;height:auto;line-height:normal;text-align:left;letter-spacing:normal}html:not(.print-pdf) h1{font-size:28pt!important}html:not(.print-pdf) h2{font-size:24pt!important}html:not(.print-pdf) h3{font-size:22pt!important}html:not(.print-pdf) h4{font-size:22pt!important;font-variant:small-caps}html:not(.print-pdf) h5{font-size:21pt!important}html:not(.print-pdf) h6{font-size:20pt!important;font-style:italic}html:not(.print-pdf) a:link,html:not(.print-pdf) a:visited{color:#000!important;font-weight:700;text-decoration:underline}html:not(.print-pdf) div,html:not(.print-pdf) ol,html:not(.print-pdf) p,html:not(.print-pdf) ul{visibility:visible;position:static;width:auto;height:auto;display:block;overflow:visible;margin:0;text-align:left!important}html:not(.print-pdf) .reveal pre,html:not(.print-pdf) .reveal table{margin-left:0;margin-right:0}html:not(.print-pdf) .reveal pre code{padding:20px}html:not(.print-pdf) .reveal blockquote{margin:20px 0}html:not(.print-pdf) .reveal .slides{position:static!important;width:auto!important;height:auto!important;left:0!important;top:0!important;margin-left:0!important;margin-top:0!important;padding:0!important;zoom:1!important;transform:none!important;overflow:visible!important;display:block!important;text-align:left!important;perspective:none;perspective-origin:50% 50%}html:not(.print-pdf) .reveal .slides section{visibility:visible!important;position:static!important;width:auto!important;height:auto!important;display:block!important;overflow:visible!important;left:0!important;top:0!important;margin-left:0!important;margin-top:0!important;padding:60px 20px!important;z-index:auto!important;opacity:1!important;page-break-after:always!important;transform-style:flat!important;transform:none!important;transition:none!important}html:not(.print-pdf) .reveal .slides section.stack{padding:0!important}html:not(.print-pdf) .reveal section:last-of-type{page-break-after:avoid!important}html:not(.print-pdf) .reveal section .fragment{opacity:1!important;visibility:visible!important;transform:none!important}html:not(.print-pdf) .reveal section img{display:block;margin:15px 0;background:#fff;border:1px solid #666;box-shadow:none}html:not(.print-pdf) .reveal section small{font-size:.8em}html:not(.print-pdf) .reveal .hljs{max-height:100%;white-space:pre-wrap;word-wrap:break-word;word-break:break-word;font-size:15pt}html:not(.print-pdf) .reveal .hljs .hljs-ln-numbers{white-space:nowrap}html:not(.print-pdf) .reveal .hljs td{font-size:inherit!important;color:inherit!important}} \ No newline at end of file diff --git a/choosing_files/libs/revealjs/dist/reveal.esm.js b/choosing_files/libs/revealjs/dist/reveal.esm.js deleted file mode 100644 index f18da89..0000000 --- a/choosing_files/libs/revealjs/dist/reveal.esm.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! -* reveal.js 4.3.1 -* https://revealjs.com -* MIT licensed -* -* Copyright (C) 2011-2022 Hakim El Hattab, https://hakim.se -*/ -const e=(e,t)=>{for(let i in t)e[i]=t[i];return e},t=(e,t)=>Array.from(e.querySelectorAll(t)),i=(e,t,i)=>{i?e.classList.add(t):e.classList.remove(t)},s=e=>{if("string"==typeof e){if("null"===e)return null;if("true"===e)return!0;if("false"===e)return!1;if(e.match(/^-?[\d\.]+$/))return parseFloat(e)}return e},a=(e,t)=>{e.style.transform=t},n=(e,t)=>{let i=e.matches||e.matchesSelector||e.msMatchesSelector;return!(!i||!i.call(e,t))},r=(e,t)=>{if("function"==typeof e.closest)return e.closest(t);for(;e;){if(n(e,t))return e;e=e.parentNode}return null},o=(e,t,i,s="")=>{let a=e.querySelectorAll("."+i);for(let t=0;t{let t=document.createElement("style");return t.type="text/css",e&&e.length>0&&(t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.createTextNode(e))),document.head.appendChild(t),t},d=()=>{let e={};location.search.replace(/[A-Z0-9]+?=([\w\.%-]*)/gi,(t=>{e[t.split("=").shift()]=t.split("=").pop()}));for(let t in e){let i=e[t];e[t]=s(unescape(i))}return void 0!==e.dependencies&&delete e.dependencies,e},c=(e,t=0)=>{if(e){let i,s=e.style.height;return e.style.height="0px",e.parentNode.style.height="auto",i=t-e.parentNode.offsetHeight,e.style.height=s+"px",e.parentNode.style.removeProperty("height"),i}return t},h={mp4:"video/mp4",m4a:"video/mp4",ogv:"video/ogg",mpeg:"video/mpeg",webm:"video/webm"},u=navigator.userAgent,g=/(iphone|ipod|ipad|android)/gi.test(u)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1;/chrome/i.test(u)&&/edge/i.test(u);const v=/android/gi.test(u);var p={};Object.defineProperty(p,"__esModule",{value:!0});var m=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return"string"==typeof e?x(t(document.querySelectorAll(e)),i):x([e],i)[0]}}("undefined"==typeof window?null:window);class b{constructor(e){this.Reveal=e,this.startEmbeddedIframe=this.startEmbeddedIframe.bind(this)}shouldPreload(e){let t=this.Reveal.getConfig().preloadIframes;return"boolean"!=typeof t&&(t=e.hasAttribute("data-preload")),t}load(e,i={}){e.style.display=this.Reveal.getConfig().display,t(e,"img[data-src], video[data-src], audio[data-src], iframe[data-src]").forEach((e=>{("IFRAME"!==e.tagName||this.shouldPreload(e))&&(e.setAttribute("src",e.getAttribute("data-src")),e.setAttribute("data-lazy-loaded",""),e.removeAttribute("data-src"))})),t(e,"video, audio").forEach((e=>{let i=0;t(e,"source[data-src]").forEach((e=>{e.setAttribute("src",e.getAttribute("data-src")),e.removeAttribute("data-src"),e.setAttribute("data-lazy-loaded",""),i+=1})),g&&"VIDEO"===e.tagName&&e.setAttribute("playsinline",""),i>0&&e.load()}));let s=e.slideBackgroundElement;if(s){s.style.display="block";let t=e.slideBackgroundContentElement,a=e.getAttribute("data-background-iframe");if(!1===s.hasAttribute("data-loaded")){s.setAttribute("data-loaded","true");let n=e.getAttribute("data-background-image"),r=e.getAttribute("data-background-video"),o=e.hasAttribute("data-background-video-loop"),l=e.hasAttribute("data-background-video-muted");if(n)/^data:/.test(n.trim())?t.style.backgroundImage=`url(${n.trim()})`:t.style.backgroundImage=n.split(",").map((e=>`url(${encodeURI(e.trim())})`)).join(",");else if(r&&!this.Reveal.isSpeakerNotes()){let e=document.createElement("video");o&&e.setAttribute("loop",""),l&&(e.muted=!0),g&&(e.muted=!0,e.setAttribute("playsinline","")),r.split(",").forEach((t=>{let i=((e="")=>h[e.split(".").pop()])(t);e.innerHTML+=i?``:``})),t.appendChild(e)}else if(a&&!0!==i.excludeIframes){let e=document.createElement("iframe");e.setAttribute("allowfullscreen",""),e.setAttribute("mozallowfullscreen",""),e.setAttribute("webkitallowfullscreen",""),e.setAttribute("allow","autoplay"),e.setAttribute("data-src",a),e.style.width="100%",e.style.height="100%",e.style.maxHeight="100%",e.style.maxWidth="100%",t.appendChild(e)}}let n=t.querySelector("iframe[data-src]");n&&this.shouldPreload(s)&&!/autoplay=(1|true|yes)/gi.test(a)&&n.getAttribute("src")!==a&&n.setAttribute("src",a)}this.layout(e)}layout(e){Array.from(e.querySelectorAll(".r-fit-text")).forEach((e=>{f(e,{minSize:24,maxSize:.8*this.Reveal.getConfig().height,observeMutations:!1,observeWindow:!1})}))}unload(e){e.style.display="none";let i=this.Reveal.getSlideBackground(e);i&&(i.style.display="none",t(i,"iframe[src]").forEach((e=>{e.removeAttribute("src")}))),t(e,"video[data-lazy-loaded][src], audio[data-lazy-loaded][src], iframe[data-lazy-loaded][src]").forEach((e=>{e.setAttribute("data-src",e.getAttribute("src")),e.removeAttribute("src")})),t(e,"video[data-lazy-loaded] source[src], audio source[src]").forEach((e=>{e.setAttribute("data-src",e.getAttribute("src")),e.removeAttribute("src")}))}formatEmbeddedContent(){let e=(e,i,s)=>{t(this.Reveal.getSlidesElement(),"iframe["+e+'*="'+i+'"]').forEach((t=>{let i=t.getAttribute(e);i&&-1===i.indexOf(s)&&t.setAttribute(e,i+(/\?/.test(i)?"&":"?")+s)}))};e("src","youtube.com/embed/","enablejsapi=1"),e("data-src","youtube.com/embed/","enablejsapi=1"),e("src","player.vimeo.com/","api=1"),e("data-src","player.vimeo.com/","api=1")}startEmbeddedContent(e){e&&!this.Reveal.isSpeakerNotes()&&(t(e,'img[src$=".gif"]').forEach((e=>{e.setAttribute("src",e.getAttribute("src"))})),t(e,"video, audio").forEach((e=>{if(r(e,".fragment")&&!r(e,".fragment.visible"))return;let t=this.Reveal.getConfig().autoPlayMedia;if("boolean"!=typeof t&&(t=e.hasAttribute("data-autoplay")||!!r(e,".slide-background")),t&&"function"==typeof e.play)if(e.readyState>1)this.startEmbeddedMedia({target:e});else if(g){let t=e.play();t&&"function"==typeof t.catch&&!1===e.controls&&t.catch((()=>{e.controls=!0,e.addEventListener("play",(()=>{e.controls=!1}))}))}else e.removeEventListener("loadeddata",this.startEmbeddedMedia),e.addEventListener("loadeddata",this.startEmbeddedMedia)})),t(e,"iframe[src]").forEach((e=>{r(e,".fragment")&&!r(e,".fragment.visible")||this.startEmbeddedIframe({target:e})})),t(e,"iframe[data-src]").forEach((e=>{r(e,".fragment")&&!r(e,".fragment.visible")||e.getAttribute("src")!==e.getAttribute("data-src")&&(e.removeEventListener("load",this.startEmbeddedIframe),e.addEventListener("load",this.startEmbeddedIframe),e.setAttribute("src",e.getAttribute("data-src")))})))}startEmbeddedMedia(e){let t=!!r(e.target,"html"),i=!!r(e.target,".present");t&&i&&(e.target.currentTime=0,e.target.play()),e.target.removeEventListener("loadeddata",this.startEmbeddedMedia)}startEmbeddedIframe(e){let t=e.target;if(t&&t.contentWindow){let i=!!r(e.target,"html"),s=!!r(e.target,".present");if(i&&s){let e=this.Reveal.getConfig().autoPlayMedia;"boolean"!=typeof e&&(e=t.hasAttribute("data-autoplay")||!!r(t,".slide-background")),/youtube\.com\/embed\//.test(t.getAttribute("src"))&&e?t.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}',"*"):/player\.vimeo\.com\//.test(t.getAttribute("src"))&&e?t.contentWindow.postMessage('{"method":"play"}',"*"):t.contentWindow.postMessage("slide:start","*")}}}stopEmbeddedContent(i,s={}){s=e({unloadIframes:!0},s),i&&i.parentNode&&(t(i,"video, audio").forEach((e=>{e.hasAttribute("data-ignore")||"function"!=typeof e.pause||(e.setAttribute("data-paused-by-reveal",""),e.pause())})),t(i,"iframe").forEach((e=>{e.contentWindow&&e.contentWindow.postMessage("slide:stop","*"),e.removeEventListener("load",this.startEmbeddedIframe)})),t(i,'iframe[src*="youtube.com/embed/"]').forEach((e=>{!e.hasAttribute("data-ignore")&&e.contentWindow&&"function"==typeof e.contentWindow.postMessage&&e.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}',"*")})),t(i,'iframe[src*="player.vimeo.com/"]').forEach((e=>{!e.hasAttribute("data-ignore")&&e.contentWindow&&"function"==typeof e.contentWindow.postMessage&&e.contentWindow.postMessage('{"method":"pause"}',"*")})),!0===s.unloadIframes&&t(i,"iframe[data-src]").forEach((e=>{e.setAttribute("src","about:blank"),e.removeAttribute("src")})))}}class y{constructor(e){this.Reveal=e}render(){this.element=document.createElement("div"),this.element.className="slide-number",this.Reveal.getRevealElement().appendChild(this.element)}configure(e,t){let i="none";e.slideNumber&&!this.Reveal.isPrintingPDF()&&("all"===e.showSlideNumber||"speaker"===e.showSlideNumber&&this.Reveal.isSpeakerNotes())&&(i="block"),this.element.style.display=i}update(){this.Reveal.getConfig().slideNumber&&this.element&&(this.element.innerHTML=this.getSlideNumber())}getSlideNumber(e=this.Reveal.getCurrentSlide()){let t,i=this.Reveal.getConfig(),s="h.v";if("function"==typeof i.slideNumber)t=i.slideNumber(e);else{"string"==typeof i.slideNumber&&(s=i.slideNumber),/c/.test(s)||1!==this.Reveal.getHorizontalSlides().length||(s="c");let a=e&&"uncounted"===e.dataset.visibility?0:1;switch(t=[],s){case"c":t.push(this.Reveal.getSlidePastCount(e)+a);break;case"c/t":t.push(this.Reveal.getSlidePastCount(e)+a,"/",this.Reveal.getTotalSlides());break;default:let i=this.Reveal.getIndices(e);t.push(i.h+a);let n="h/v"===s?"/":".";this.Reveal.isVerticalSlide(e)&&t.push(n,i.v+1)}}let a="#"+this.Reveal.location.getHash(e);return this.formatNumber(t[0],t[1],t[2],a)}formatNumber(e,t,i,s="#"+this.Reveal.location.getHash()){return"number"!=typeof i||isNaN(i)?`\n\t\t\t\t\t${e}\n\t\t\t\t\t`:`\n\t\t\t\t\t${e}\n\t\t\t\t\t${t}\n\t\t\t\t\t${i}\n\t\t\t\t\t`}destroy(){this.element.remove()}}const w=e=>{let t=e.match(/^#([0-9a-f]{3})$/i);if(t&&t[1])return t=t[1],{r:17*parseInt(t.charAt(0),16),g:17*parseInt(t.charAt(1),16),b:17*parseInt(t.charAt(2),16)};let i=e.match(/^#([0-9a-f]{6})$/i);if(i&&i[1])return i=i[1],{r:parseInt(i.slice(0,2),16),g:parseInt(i.slice(2,4),16),b:parseInt(i.slice(4,6),16)};let s=e.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);if(s)return{r:parseInt(s[1],10),g:parseInt(s[2],10),b:parseInt(s[3],10)};let a=e.match(/^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i);return a?{r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10),a:parseFloat(a[4])}:null};class E{constructor(e){this.Reveal=e}render(){this.element=document.createElement("div"),this.element.className="backgrounds",this.Reveal.getRevealElement().appendChild(this.element)}create(){this.element.innerHTML="",this.element.classList.add("no-transition"),this.Reveal.getHorizontalSlides().forEach((e=>{let i=this.createBackground(e,this.element);t(e,"section").forEach((e=>{this.createBackground(e,i),i.classList.add("stack")}))})),this.Reveal.getConfig().parallaxBackgroundImage?(this.element.style.backgroundImage='url("'+this.Reveal.getConfig().parallaxBackgroundImage+'")',this.element.style.backgroundSize=this.Reveal.getConfig().parallaxBackgroundSize,this.element.style.backgroundRepeat=this.Reveal.getConfig().parallaxBackgroundRepeat,this.element.style.backgroundPosition=this.Reveal.getConfig().parallaxBackgroundPosition,setTimeout((()=>{this.Reveal.getRevealElement().classList.add("has-parallax-background")}),1)):(this.element.style.backgroundImage="",this.Reveal.getRevealElement().classList.remove("has-parallax-background"))}createBackground(e,t){let i=document.createElement("div");i.className="slide-background "+e.className.replace(/present|past|future/,"");let s=document.createElement("div");return s.className="slide-background-content",i.appendChild(s),t.appendChild(i),e.slideBackgroundElement=i,e.slideBackgroundContentElement=s,this.sync(e),i}sync(e){const t=e.slideBackgroundElement,i=e.slideBackgroundContentElement,s={background:e.getAttribute("data-background"),backgroundSize:e.getAttribute("data-background-size"),backgroundImage:e.getAttribute("data-background-image"),backgroundVideo:e.getAttribute("data-background-video"),backgroundIframe:e.getAttribute("data-background-iframe"),backgroundColor:e.getAttribute("data-background-color"),backgroundRepeat:e.getAttribute("data-background-repeat"),backgroundPosition:e.getAttribute("data-background-position"),backgroundTransition:e.getAttribute("data-background-transition"),backgroundOpacity:e.getAttribute("data-background-opacity")},a=e.hasAttribute("data-preload");e.classList.remove("has-dark-background"),e.classList.remove("has-light-background"),t.removeAttribute("data-loaded"),t.removeAttribute("data-background-hash"),t.removeAttribute("data-background-size"),t.removeAttribute("data-background-transition"),t.style.backgroundColor="",i.style.backgroundSize="",i.style.backgroundRepeat="",i.style.backgroundPosition="",i.style.backgroundImage="",i.style.opacity="",i.innerHTML="",s.background&&(/^(http|file|\/\/)/gi.test(s.background)||/\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test(s.background)?e.setAttribute("data-background-image",s.background):t.style.background=s.background),(s.background||s.backgroundColor||s.backgroundImage||s.backgroundVideo||s.backgroundIframe)&&t.setAttribute("data-background-hash",s.background+s.backgroundSize+s.backgroundImage+s.backgroundVideo+s.backgroundIframe+s.backgroundColor+s.backgroundRepeat+s.backgroundPosition+s.backgroundTransition+s.backgroundOpacity),s.backgroundSize&&t.setAttribute("data-background-size",s.backgroundSize),s.backgroundColor&&(t.style.backgroundColor=s.backgroundColor),s.backgroundTransition&&t.setAttribute("data-background-transition",s.backgroundTransition),a&&t.setAttribute("data-preload",""),s.backgroundSize&&(i.style.backgroundSize=s.backgroundSize),s.backgroundRepeat&&(i.style.backgroundRepeat=s.backgroundRepeat),s.backgroundPosition&&(i.style.backgroundPosition=s.backgroundPosition),s.backgroundOpacity&&(i.style.opacity=s.backgroundOpacity);let n=s.backgroundColor;if(!n||!w(n)){let e=window.getComputedStyle(t);e&&e.backgroundColor&&(n=e.backgroundColor)}if(n){const t=w(n);t&&0!==t.a&&("string"==typeof(r=n)&&(r=w(r)),(r?(299*r.r+587*r.g+114*r.b)/1e3:null)<128?e.classList.add("has-dark-background"):e.classList.add("has-light-background"))}var r}update(e=!1){let i=this.Reveal.getCurrentSlide(),s=this.Reveal.getIndices(),a=null,n=this.Reveal.getConfig().rtl?"future":"past",r=this.Reveal.getConfig().rtl?"past":"future";if(Array.from(this.element.childNodes).forEach(((i,o)=>{i.classList.remove("past","present","future"),os.h?i.classList.add(r):(i.classList.add("present"),a=i),(e||o===s.h)&&t(i,".slide-background").forEach(((e,t)=>{e.classList.remove("past","present","future"),ts.v?e.classList.add("future"):(e.classList.add("present"),o===s.h&&(a=e))}))})),this.previousBackground&&this.Reveal.slideContent.stopEmbeddedContent(this.previousBackground,{unloadIframes:!this.Reveal.slideContent.shouldPreload(this.previousBackground)}),a){this.Reveal.slideContent.startEmbeddedContent(a);let e=a.querySelector(".slide-background-content");if(e){let t=e.style.backgroundImage||"";/\.gif/i.test(t)&&(e.style.backgroundImage="",window.getComputedStyle(e).opacity,e.style.backgroundImage=t)}let t=this.previousBackground?this.previousBackground.getAttribute("data-background-hash"):null,i=a.getAttribute("data-background-hash");i&&i===t&&a!==this.previousBackground&&this.element.classList.add("no-transition"),this.previousBackground=a}i&&["has-light-background","has-dark-background"].forEach((e=>{i.classList.contains(e)?this.Reveal.getRevealElement().classList.add(e):this.Reveal.getRevealElement().classList.remove(e)}),this),setTimeout((()=>{this.element.classList.remove("no-transition")}),1)}updateParallax(){let e=this.Reveal.getIndices();if(this.Reveal.getConfig().parallaxBackgroundImage){let t,i,s=this.Reveal.getHorizontalSlides(),a=this.Reveal.getVerticalSlides(),n=this.element.style.backgroundSize.split(" ");1===n.length?t=i=parseInt(n[0],10):(t=parseInt(n[0],10),i=parseInt(n[1],10));let r,o,l=this.element.offsetWidth,d=s.length;r="number"==typeof this.Reveal.getConfig().parallaxBackgroundHorizontal?this.Reveal.getConfig().parallaxBackgroundHorizontal:d>1?(t-l)/(d-1):0,o=r*e.h*-1;let c,h,u=this.element.offsetHeight,g=a.length;c="number"==typeof this.Reveal.getConfig().parallaxBackgroundVertical?this.Reveal.getConfig().parallaxBackgroundVertical:(i-u)/(g-1),h=g>0?c*e.v:0,this.element.style.backgroundPosition=o+"px "+-h+"px"}}destroy(){this.element.remove()}}const R=/registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener/,S=/fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/;let A=0;class k{constructor(e){this.Reveal=e}run(e,t){this.reset();let i=this.Reveal.getSlides(),s=i.indexOf(t),a=i.indexOf(e);if(e.hasAttribute("data-auto-animate")&&t.hasAttribute("data-auto-animate")&&e.getAttribute("data-auto-animate-id")===t.getAttribute("data-auto-animate-id")&&!(s>a?t:e).hasAttribute("data-auto-animate-restart")){this.autoAnimateStyleSheet=this.autoAnimateStyleSheet||l();let i=this.getAutoAnimateOptions(t);e.dataset.autoAnimate="pending",t.dataset.autoAnimate="pending",i.slideDirection=s>a?"forward":"backward";let n=this.getAutoAnimatableElements(e,t).map((e=>this.autoAnimateElements(e.from,e.to,e.options||{},i,A++)));if("false"!==t.dataset.autoAnimateUnmatched&&!0===this.Reveal.getConfig().autoAnimateUnmatched){let e=.8*i.duration,s=.2*i.duration;this.getUnmatchedAutoAnimateElements(t).forEach((e=>{let t=this.getAutoAnimateOptions(e,i),s="unmatched";t.duration===i.duration&&t.delay===i.delay||(s="unmatched-"+A++,n.push(`[data-auto-animate="running"] [data-auto-animate-target="${s}"] { transition: opacity ${t.duration}s ease ${t.delay}s; }`)),e.dataset.autoAnimateTarget=s}),this),n.push(`[data-auto-animate="running"] [data-auto-animate-target="unmatched"] { transition: opacity ${e}s ease ${s}s; }`)}this.autoAnimateStyleSheet.innerHTML=n.join(""),requestAnimationFrame((()=>{this.autoAnimateStyleSheet&&(getComputedStyle(this.autoAnimateStyleSheet).fontWeight,t.dataset.autoAnimate="running")})),this.Reveal.dispatchEvent({type:"autoanimate",data:{fromSlide:e,toSlide:t,sheet:this.autoAnimateStyleSheet}})}}reset(){t(this.Reveal.getRevealElement(),'[data-auto-animate]:not([data-auto-animate=""])').forEach((e=>{e.dataset.autoAnimate=""})),t(this.Reveal.getRevealElement(),"[data-auto-animate-target]").forEach((e=>{delete e.dataset.autoAnimateTarget})),this.autoAnimateStyleSheet&&this.autoAnimateStyleSheet.parentNode&&(this.autoAnimateStyleSheet.parentNode.removeChild(this.autoAnimateStyleSheet),this.autoAnimateStyleSheet=null)}autoAnimateElements(e,t,i,s,a){e.dataset.autoAnimateTarget="",t.dataset.autoAnimateTarget=a;let n=this.getAutoAnimateOptions(t,s);void 0!==i.delay&&(n.delay=i.delay),void 0!==i.duration&&(n.duration=i.duration),void 0!==i.easing&&(n.easing=i.easing);let r=this.getAutoAnimatableProperties("from",e,i),o=this.getAutoAnimatableProperties("to",t,i);if(t.classList.contains("fragment")&&(delete o.styles.opacity,e.classList.contains("fragment"))){(e.className.match(S)||[""])[0]===(t.className.match(S)||[""])[0]&&"forward"===s.slideDirection&&t.classList.add("visible","disabled")}if(!1!==i.translate||!1!==i.scale){let e=this.Reveal.getScale(),t={x:(r.x-o.x)/e,y:(r.y-o.y)/e,scaleX:r.width/o.width,scaleY:r.height/o.height};t.x=Math.round(1e3*t.x)/1e3,t.y=Math.round(1e3*t.y)/1e3,t.scaleX=Math.round(1e3*t.scaleX)/1e3,t.scaleX=Math.round(1e3*t.scaleX)/1e3;let s=!1!==i.translate&&(0!==t.x||0!==t.y),a=!1!==i.scale&&(0!==t.scaleX||0!==t.scaleY);if(s||a){let e=[];s&&e.push(`translate(${t.x}px, ${t.y}px)`),a&&e.push(`scale(${t.scaleX}, ${t.scaleY})`),r.styles.transform=e.join(" "),r.styles["transform-origin"]="top left",o.styles.transform="none"}}for(let e in o.styles){const t=o.styles[e],i=r.styles[e];t===i?delete o.styles[e]:(!0===t.explicitValue&&(o.styles[e]=t.value),!0===i.explicitValue&&(r.styles[e]=i.value))}let l="",d=Object.keys(o.styles);if(d.length>0){r.styles.transition="none",o.styles.transition=`all ${n.duration}s ${n.easing} ${n.delay}s`,o.styles["transition-property"]=d.join(", "),o.styles["will-change"]=d.join(", "),l='[data-auto-animate-target="'+a+'"] {'+Object.keys(r.styles).map((e=>e+": "+r.styles[e]+" !important;")).join("")+'}[data-auto-animate="running"] [data-auto-animate-target="'+a+'"] {'+Object.keys(o.styles).map((e=>e+": "+o.styles[e]+" !important;")).join("")+"}"}return l}getAutoAnimateOptions(t,i){let s={easing:this.Reveal.getConfig().autoAnimateEasing,duration:this.Reveal.getConfig().autoAnimateDuration,delay:0};if(s=e(s,i),t.parentNode){let e=r(t.parentNode,"[data-auto-animate-target]");e&&(s=this.getAutoAnimateOptions(e,s))}return t.dataset.autoAnimateEasing&&(s.easing=t.dataset.autoAnimateEasing),t.dataset.autoAnimateDuration&&(s.duration=parseFloat(t.dataset.autoAnimateDuration)),t.dataset.autoAnimateDelay&&(s.delay=parseFloat(t.dataset.autoAnimateDelay)),s}getAutoAnimatableProperties(e,t,i){let s=this.Reveal.getConfig(),a={styles:[]};if(!1!==i.translate||!1!==i.scale){let e;if("function"==typeof i.measure)e=i.measure(t);else if(s.center)e=t.getBoundingClientRect();else{let i=this.Reveal.getScale();e={x:t.offsetLeft*i,y:t.offsetTop*i,width:t.offsetWidth*i,height:t.offsetHeight*i}}a.x=e.x,a.y=e.y,a.width=e.width,a.height=e.height}const n=getComputedStyle(t);return(i.styles||s.autoAnimateStyles).forEach((t=>{let i;"string"==typeof t&&(t={property:t}),i=void 0!==t.from&&"from"===e?{value:t.from,explicitValue:!0}:void 0!==t.to&&"to"===e?{value:t.to,explicitValue:!0}:n[t.property],""!==i&&(a.styles[t.property]=i)})),a}getAutoAnimatableElements(e,t){let i=("function"==typeof this.Reveal.getConfig().autoAnimateMatcher?this.Reveal.getConfig().autoAnimateMatcher:this.getAutoAnimatePairs).call(this,e,t),s=[];return i.filter(((e,t)=>{if(-1===s.indexOf(e.to))return s.push(e.to),!0}))}getAutoAnimatePairs(e,t){let i=[];const s="h1, h2, h3, h4, h5, h6, p, li";return this.findAutoAnimateMatches(i,e,t,"[data-id]",(e=>e.nodeName+":::"+e.getAttribute("data-id"))),this.findAutoAnimateMatches(i,e,t,s,(e=>e.nodeName+":::"+e.innerText)),this.findAutoAnimateMatches(i,e,t,"img, video, iframe",(e=>e.nodeName+":::"+(e.getAttribute("src")||e.getAttribute("data-src")))),this.findAutoAnimateMatches(i,e,t,"pre",(e=>e.nodeName+":::"+e.innerText)),i.forEach((e=>{n(e.from,s)?e.options={scale:!1}:n(e.from,"pre")&&(e.options={scale:!1,styles:["width","height"]},this.findAutoAnimateMatches(i,e.from,e.to,".hljs .hljs-ln-code",(e=>e.textContent),{scale:!1,styles:[],measure:this.getLocalBoundingBox.bind(this)}),this.findAutoAnimateMatches(i,e.from,e.to,".hljs .hljs-ln-line[data-line-number]",(e=>e.getAttribute("data-line-number")),{scale:!1,styles:["width"],measure:this.getLocalBoundingBox.bind(this)}))}),this),i}getLocalBoundingBox(e){const t=this.Reveal.getScale();return{x:Math.round(e.offsetLeft*t*100)/100,y:Math.round(e.offsetTop*t*100)/100,width:Math.round(e.offsetWidth*t*100)/100,height:Math.round(e.offsetHeight*t*100)/100}}findAutoAnimateMatches(e,t,i,s,a,n){let r={},o={};[].slice.call(t.querySelectorAll(s)).forEach(((e,t)=>{const i=a(e);"string"==typeof i&&i.length&&(r[i]=r[i]||[],r[i].push(e))})),[].slice.call(i.querySelectorAll(s)).forEach(((t,i)=>{const s=a(t);let l;if(o[s]=o[s]||[],o[s].push(t),r[s]){const e=o[s].length-1,t=r[s].length-1;r[s][e]?(l=r[s][e],r[s][e]=null):r[s][t]&&(l=r[s][t],r[s][t]=null)}l&&e.push({from:l,to:t,options:n})}))}getUnmatchedAutoAnimateElements(e){return[].slice.call(e.children).reduce(((e,t)=>{const i=t.querySelector("[data-auto-animate-target]");return t.hasAttribute("data-auto-animate-target")||i||e.push(t),t.querySelector("[data-auto-animate-target]")&&(e=e.concat(this.getUnmatchedAutoAnimateElements(t))),e}),[])}}class L{constructor(e){this.Reveal=e}configure(e,t){!1===e.fragments?this.disable():!1===t.fragments&&this.enable()}disable(){t(this.Reveal.getSlidesElement(),".fragment").forEach((e=>{e.classList.add("visible"),e.classList.remove("current-fragment")}))}enable(){t(this.Reveal.getSlidesElement(),".fragment").forEach((e=>{e.classList.remove("visible"),e.classList.remove("current-fragment")}))}availableRoutes(){let e=this.Reveal.getCurrentSlide();if(e&&this.Reveal.getConfig().fragments){let t=e.querySelectorAll(".fragment:not(.disabled)"),i=e.querySelectorAll(".fragment:not(.disabled):not(.visible)");return{prev:t.length-i.length>0,next:!!i.length}}return{prev:!1,next:!1}}sort(e,t=!1){e=Array.from(e);let i=[],s=[],a=[];e.forEach((e=>{if(e.hasAttribute("data-fragment-index")){let t=parseInt(e.getAttribute("data-fragment-index"),10);i[t]||(i[t]=[]),i[t].push(e)}else s.push([e])})),i=i.concat(s);let n=0;return i.forEach((e=>{e.forEach((e=>{a.push(e),e.setAttribute("data-fragment-index",n)})),n++})),!0===t?i:a}sortAll(){this.Reveal.getHorizontalSlides().forEach((e=>{let i=t(e,"section");i.forEach(((e,t)=>{this.sort(e.querySelectorAll(".fragment"))}),this),0===i.length&&this.sort(e.querySelectorAll(".fragment"))}))}update(e,t){let i={shown:[],hidden:[]},s=this.Reveal.getCurrentSlide();if(s&&this.Reveal.getConfig().fragments&&(t=t||this.sort(s.querySelectorAll(".fragment"))).length){let a=0;if("number"!=typeof e){let t=this.sort(s.querySelectorAll(".fragment.visible")).pop();t&&(e=parseInt(t.getAttribute("data-fragment-index")||0,10))}Array.from(t).forEach(((t,s)=>{if(t.hasAttribute("data-fragment-index")&&(s=parseInt(t.getAttribute("data-fragment-index"),10)),a=Math.max(a,s),s<=e){let a=t.classList.contains("visible");t.classList.add("visible"),t.classList.remove("current-fragment"),s===e&&(this.Reveal.announceStatus(this.Reveal.getStatusText(t)),t.classList.add("current-fragment"),this.Reveal.slideContent.startEmbeddedContent(t)),a||(i.shown.push(t),this.Reveal.dispatchEvent({target:t,type:"visible",bubbles:!1}))}else{let e=t.classList.contains("visible");t.classList.remove("visible"),t.classList.remove("current-fragment"),e&&(this.Reveal.slideContent.stopEmbeddedContent(t),i.hidden.push(t),this.Reveal.dispatchEvent({target:t,type:"hidden",bubbles:!1}))}})),e="number"==typeof e?e:-1,e=Math.max(Math.min(e,a),-1),s.setAttribute("data-fragment",e)}return i}sync(e=this.Reveal.getCurrentSlide()){return this.sort(e.querySelectorAll(".fragment"))}goto(e,t=0){let i=this.Reveal.getCurrentSlide();if(i&&this.Reveal.getConfig().fragments){let s=this.sort(i.querySelectorAll(".fragment:not(.disabled)"));if(s.length){if("number"!=typeof e){let t=this.sort(i.querySelectorAll(".fragment:not(.disabled).visible")).pop();e=t?parseInt(t.getAttribute("data-fragment-index")||0,10):-1}e+=t;let a=this.update(e,s);return a.hidden.length&&this.Reveal.dispatchEvent({type:"fragmenthidden",data:{fragment:a.hidden[0],fragments:a.hidden}}),a.shown.length&&this.Reveal.dispatchEvent({type:"fragmentshown",data:{fragment:a.shown[0],fragments:a.shown}}),this.Reveal.controls.update(),this.Reveal.progress.update(),this.Reveal.getConfig().fragmentInURL&&this.Reveal.location.writeURL(),!(!a.shown.length&&!a.hidden.length)}}return!1}next(){return this.goto(null,1)}prev(){return this.goto(null,-1)}}class C{constructor(e){this.Reveal=e,this.active=!1,this.onSlideClicked=this.onSlideClicked.bind(this)}activate(){if(this.Reveal.getConfig().overview&&!this.isActive()){this.active=!0,this.Reveal.getRevealElement().classList.add("overview"),this.Reveal.cancelAutoSlide(),this.Reveal.getSlidesElement().appendChild(this.Reveal.getBackgroundsElement()),t(this.Reveal.getRevealElement(),".slides section").forEach((e=>{e.classList.contains("stack")||e.addEventListener("click",this.onSlideClicked,!0)}));const e=70,i=this.Reveal.getComputedSlideSize();this.overviewSlideWidth=i.width+e,this.overviewSlideHeight=i.height+e,this.Reveal.getConfig().rtl&&(this.overviewSlideWidth=-this.overviewSlideWidth),this.Reveal.updateSlidesVisibility(),this.layout(),this.update(),this.Reveal.layout();const s=this.Reveal.getIndices();this.Reveal.dispatchEvent({type:"overviewshown",data:{indexh:s.h,indexv:s.v,currentSlide:this.Reveal.getCurrentSlide()}})}}layout(){this.Reveal.getHorizontalSlides().forEach(((e,i)=>{e.setAttribute("data-index-h",i),a(e,"translate3d("+i*this.overviewSlideWidth+"px, 0, 0)"),e.classList.contains("stack")&&t(e,"section").forEach(((e,t)=>{e.setAttribute("data-index-h",i),e.setAttribute("data-index-v",t),a(e,"translate3d(0, "+t*this.overviewSlideHeight+"px, 0)")}))})),Array.from(this.Reveal.getBackgroundsElement().childNodes).forEach(((e,i)=>{a(e,"translate3d("+i*this.overviewSlideWidth+"px, 0, 0)"),t(e,".slide-background").forEach(((e,t)=>{a(e,"translate3d(0, "+t*this.overviewSlideHeight+"px, 0)")}))}))}update(){const e=Math.min(window.innerWidth,window.innerHeight),t=Math.max(e/5,150)/e,i=this.Reveal.getIndices();this.Reveal.transformSlides({overview:["scale("+t+")","translateX("+-i.h*this.overviewSlideWidth+"px)","translateY("+-i.v*this.overviewSlideHeight+"px)"].join(" ")})}deactivate(){if(this.Reveal.getConfig().overview){this.active=!1,this.Reveal.getRevealElement().classList.remove("overview"),this.Reveal.getRevealElement().classList.add("overview-deactivating"),setTimeout((()=>{this.Reveal.getRevealElement().classList.remove("overview-deactivating")}),1),this.Reveal.getRevealElement().appendChild(this.Reveal.getBackgroundsElement()),t(this.Reveal.getRevealElement(),".slides section").forEach((e=>{a(e,""),e.removeEventListener("click",this.onSlideClicked,!0)})),t(this.Reveal.getBackgroundsElement(),".slide-background").forEach((e=>{a(e,"")})),this.Reveal.transformSlides({overview:""});const e=this.Reveal.getIndices();this.Reveal.slide(e.h,e.v),this.Reveal.layout(),this.Reveal.cueAutoSlide(),this.Reveal.dispatchEvent({type:"overviewhidden",data:{indexh:e.h,indexv:e.v,currentSlide:this.Reveal.getCurrentSlide()}})}}toggle(e){"boolean"==typeof e?e?this.activate():this.deactivate():this.isActive()?this.deactivate():this.activate()}isActive(){return this.active}onSlideClicked(e){if(this.isActive()){e.preventDefault();let t=e.target;for(;t&&!t.nodeName.match(/section/gi);)t=t.parentNode;if(t&&!t.classList.contains("disabled")&&(this.deactivate(),t.nodeName.match(/section/gi))){let e=parseInt(t.getAttribute("data-index-h"),10),i=parseInt(t.getAttribute("data-index-v"),10);this.Reveal.slide(e,i)}}}}class x{constructor(e){this.Reveal=e,this.shortcuts={},this.bindings={},this.onDocumentKeyDown=this.onDocumentKeyDown.bind(this),this.onDocumentKeyPress=this.onDocumentKeyPress.bind(this)}configure(e,t){"linear"===e.navigationMode?(this.shortcuts["→ , ↓ , SPACE , N , L , J"]="Next slide",this.shortcuts["← , ↑ , P , H , K"]="Previous slide"):(this.shortcuts["N , SPACE"]="Next slide",this.shortcuts["P , Shift SPACE"]="Previous slide",this.shortcuts["← , H"]="Navigate left",this.shortcuts["→ , L"]="Navigate right",this.shortcuts["↑ , K"]="Navigate up",this.shortcuts["↓ , J"]="Navigate down"),this.shortcuts["Alt + ←/↑/→/↓"]="Navigate without fragments",this.shortcuts["Shift + ←/↑/→/↓"]="Jump to first/last slide",this.shortcuts["B , ."]="Pause",this.shortcuts.F="Fullscreen",this.shortcuts["ESC, O"]="Slide overview"}bind(){document.addEventListener("keydown",this.onDocumentKeyDown,!1),document.addEventListener("keypress",this.onDocumentKeyPress,!1)}unbind(){document.removeEventListener("keydown",this.onDocumentKeyDown,!1),document.removeEventListener("keypress",this.onDocumentKeyPress,!1)}addKeyBinding(e,t){"object"==typeof e&&e.keyCode?this.bindings[e.keyCode]={callback:t,key:e.key,description:e.description}:this.bindings[e]={callback:t,key:null,description:null}}removeKeyBinding(e){delete this.bindings[e]}triggerKey(e){this.onDocumentKeyDown({keyCode:e})}registerKeyboardShortcut(e,t){this.shortcuts[e]=t}getShortcuts(){return this.shortcuts}getBindings(){return this.bindings}onDocumentKeyPress(e){e.shiftKey&&63===e.charCode&&this.Reveal.toggleHelp()}onDocumentKeyDown(e){let t=this.Reveal.getConfig();if("function"==typeof t.keyboardCondition&&!1===t.keyboardCondition(e))return!0;if("focused"===t.keyboardCondition&&!this.Reveal.isFocused())return!0;let i=e.keyCode,s=!this.Reveal.isAutoSliding();this.Reveal.onUserInput(e);let a=document.activeElement&&!0===document.activeElement.isContentEditable,n=document.activeElement&&document.activeElement.tagName&&/input|textarea/i.test(document.activeElement.tagName),r=document.activeElement&&document.activeElement.className&&/speaker-notes/i.test(document.activeElement.className),o=!(-1!==[32,37,38,39,40,78,80].indexOf(e.keyCode)&&e.shiftKey||e.altKey)&&(e.shiftKey||e.altKey||e.ctrlKey||e.metaKey);if(a||n||r||o)return;let l,d=[66,86,190,191];if("object"==typeof t.keyboard)for(l in t.keyboard)"togglePause"===t.keyboard[l]&&d.push(parseInt(l,10));if(this.Reveal.isPaused()&&-1===d.indexOf(i))return!1;let c="linear"===t.navigationMode||!this.Reveal.hasHorizontalSlides()||!this.Reveal.hasVerticalSlides(),h=!1;if("object"==typeof t.keyboard)for(l in t.keyboard)if(parseInt(l,10)===i){let i=t.keyboard[l];"function"==typeof i?i.apply(null,[e]):"string"==typeof i&&"function"==typeof this.Reveal[i]&&this.Reveal[i].call(),h=!0}if(!1===h)for(l in this.bindings)if(parseInt(l,10)===i){let t=this.bindings[l].callback;"function"==typeof t?t.apply(null,[e]):"string"==typeof t&&"function"==typeof this.Reveal[t]&&this.Reveal[t].call(),h=!0}!1===h&&(h=!0,80===i||33===i?this.Reveal.prev({skipFragments:e.altKey}):78===i||34===i?this.Reveal.next({skipFragments:e.altKey}):72===i||37===i?e.shiftKey?this.Reveal.slide(0):!this.Reveal.overview.isActive()&&c?this.Reveal.prev({skipFragments:e.altKey}):this.Reveal.left({skipFragments:e.altKey}):76===i||39===i?e.shiftKey?this.Reveal.slide(this.Reveal.getHorizontalSlides().length-1):!this.Reveal.overview.isActive()&&c?this.Reveal.next({skipFragments:e.altKey}):this.Reveal.right({skipFragments:e.altKey}):75===i||38===i?e.shiftKey?this.Reveal.slide(void 0,0):!this.Reveal.overview.isActive()&&c?this.Reveal.prev({skipFragments:e.altKey}):this.Reveal.up({skipFragments:e.altKey}):74===i||40===i?e.shiftKey?this.Reveal.slide(void 0,Number.MAX_VALUE):!this.Reveal.overview.isActive()&&c?this.Reveal.next({skipFragments:e.altKey}):this.Reveal.down({skipFragments:e.altKey}):36===i?this.Reveal.slide(0):35===i?this.Reveal.slide(this.Reveal.getHorizontalSlides().length-1):32===i?(this.Reveal.overview.isActive()&&this.Reveal.overview.deactivate(),e.shiftKey?this.Reveal.prev({skipFragments:e.altKey}):this.Reveal.next({skipFragments:e.altKey})):58===i||59===i||66===i||86===i||190===i||191===i?this.Reveal.togglePause():70===i?(e=>{let t=(e=e||document.documentElement).requestFullscreen||e.webkitRequestFullscreen||e.webkitRequestFullScreen||e.mozRequestFullScreen||e.msRequestFullscreen;t&&t.apply(e)})(t.embedded?this.Reveal.getViewportElement():document.documentElement):65===i?t.autoSlideStoppable&&this.Reveal.toggleAutoSlide(s):h=!1),h?e.preventDefault&&e.preventDefault():27!==i&&79!==i||(!1===this.Reveal.closeOverlay()&&this.Reveal.overview.toggle(),e.preventDefault&&e.preventDefault()),this.Reveal.cueAutoSlide()}}class P{constructor(e){var t,i,s;s=1e3,(i="MAX_REPLACE_STATE_FREQUENCY")in(t=this)?Object.defineProperty(t,i,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[i]=s,this.Reveal=e,this.writeURLTimeout=0,this.replaceStateTimestamp=0,this.onWindowHashChange=this.onWindowHashChange.bind(this)}bind(){window.addEventListener("hashchange",this.onWindowHashChange,!1)}unbind(){window.removeEventListener("hashchange",this.onWindowHashChange,!1)}getIndicesFromHash(e=window.location.hash){let t=e.replace(/^#\/?/,""),i=t.split("/");if(/^[0-9]*$/.test(i[0])||!t.length){const e=this.Reveal.getConfig();let t,s=e.hashOneBasedIndex?1:0,a=parseInt(i[0],10)-s||0,n=parseInt(i[1],10)-s||0;return e.fragmentInURL&&(t=parseInt(i[2],10),isNaN(t)&&(t=void 0)),{h:a,v:n,f:t}}{let e,i;/\/[-\d]+$/g.test(t)&&(i=parseInt(t.split("/").pop(),10),i=isNaN(i)?void 0:i,t=t.split("/").shift());try{e=document.getElementById(decodeURIComponent(t))}catch(e){}if(e)return{...this.Reveal.getIndices(e),f:i}}return null}readURL(){const e=this.Reveal.getIndices(),t=this.getIndicesFromHash();t?t.h===e.h&&t.v===e.v&&void 0===t.f||this.Reveal.slide(t.h,t.v,t.f):this.Reveal.slide(e.h||0,e.v||0)}writeURL(e){let t=this.Reveal.getConfig(),i=this.Reveal.getCurrentSlide();if(clearTimeout(this.writeURLTimeout),"number"==typeof e)this.writeURLTimeout=setTimeout(this.writeURL,e);else if(i){let e=this.getHash();t.history?window.location.hash=e:t.hash&&("/"===e?this.debouncedReplaceState(window.location.pathname+window.location.search):this.debouncedReplaceState("#"+e))}}replaceState(e){window.history.replaceState(null,null,e),this.replaceStateTimestamp=Date.now()}debouncedReplaceState(e){clearTimeout(this.replaceStateTimeout),Date.now()-this.replaceStateTimestamp>this.MAX_REPLACE_STATE_FREQUENCY?this.replaceState(e):this.replaceStateTimeout=setTimeout((()=>this.replaceState(e)),this.MAX_REPLACE_STATE_FREQUENCY)}getHash(e){let t="/",i=e||this.Reveal.getCurrentSlide(),s=i?i.getAttribute("id"):null;s&&(s=encodeURIComponent(s));let a=this.Reveal.getIndices(e);if(this.Reveal.getConfig().fragmentInURL||(a.f=void 0),"string"==typeof s&&s.length)t="/"+s,a.f>=0&&(t+="/"+a.f);else{let e=this.Reveal.getConfig().hashOneBasedIndex?1:0;(a.h>0||a.v>0||a.f>=0)&&(t+=a.h+e),(a.v>0||a.f>=0)&&(t+="/"+(a.v+e)),a.f>=0&&(t+="/"+a.f)}return t}onWindowHashChange(e){this.readURL()}}class N{constructor(e){this.Reveal=e,this.onNavigateLeftClicked=this.onNavigateLeftClicked.bind(this),this.onNavigateRightClicked=this.onNavigateRightClicked.bind(this),this.onNavigateUpClicked=this.onNavigateUpClicked.bind(this),this.onNavigateDownClicked=this.onNavigateDownClicked.bind(this),this.onNavigatePrevClicked=this.onNavigatePrevClicked.bind(this),this.onNavigateNextClicked=this.onNavigateNextClicked.bind(this)}render(){const e=this.Reveal.getConfig().rtl,i=this.Reveal.getRevealElement();this.element=document.createElement("aside"),this.element.className="controls",this.element.innerHTML=`\n\t\t\t\n\t\t\t\n\t\t\t`,this.Reveal.getRevealElement().appendChild(this.element),this.controlsLeft=t(i,".navigate-left"),this.controlsRight=t(i,".navigate-right"),this.controlsUp=t(i,".navigate-up"),this.controlsDown=t(i,".navigate-down"),this.controlsPrev=t(i,".navigate-prev"),this.controlsNext=t(i,".navigate-next"),this.controlsRightArrow=this.element.querySelector(".navigate-right"),this.controlsLeftArrow=this.element.querySelector(".navigate-left"),this.controlsDownArrow=this.element.querySelector(".navigate-down")}configure(e,t){this.element.style.display=e.controls?"block":"none",this.element.setAttribute("data-controls-layout",e.controlsLayout),this.element.setAttribute("data-controls-back-arrows",e.controlsBackArrows)}bind(){let e=["touchstart","click"];v&&(e=["touchstart"]),e.forEach((e=>{this.controlsLeft.forEach((t=>t.addEventListener(e,this.onNavigateLeftClicked,!1))),this.controlsRight.forEach((t=>t.addEventListener(e,this.onNavigateRightClicked,!1))),this.controlsUp.forEach((t=>t.addEventListener(e,this.onNavigateUpClicked,!1))),this.controlsDown.forEach((t=>t.addEventListener(e,this.onNavigateDownClicked,!1))),this.controlsPrev.forEach((t=>t.addEventListener(e,this.onNavigatePrevClicked,!1))),this.controlsNext.forEach((t=>t.addEventListener(e,this.onNavigateNextClicked,!1)))}))}unbind(){["touchstart","click"].forEach((e=>{this.controlsLeft.forEach((t=>t.removeEventListener(e,this.onNavigateLeftClicked,!1))),this.controlsRight.forEach((t=>t.removeEventListener(e,this.onNavigateRightClicked,!1))),this.controlsUp.forEach((t=>t.removeEventListener(e,this.onNavigateUpClicked,!1))),this.controlsDown.forEach((t=>t.removeEventListener(e,this.onNavigateDownClicked,!1))),this.controlsPrev.forEach((t=>t.removeEventListener(e,this.onNavigatePrevClicked,!1))),this.controlsNext.forEach((t=>t.removeEventListener(e,this.onNavigateNextClicked,!1)))}))}update(){let e=this.Reveal.availableRoutes();[...this.controlsLeft,...this.controlsRight,...this.controlsUp,...this.controlsDown,...this.controlsPrev,...this.controlsNext].forEach((e=>{e.classList.remove("enabled","fragmented"),e.setAttribute("disabled","disabled")})),e.left&&this.controlsLeft.forEach((e=>{e.classList.add("enabled"),e.removeAttribute("disabled")})),e.right&&this.controlsRight.forEach((e=>{e.classList.add("enabled"),e.removeAttribute("disabled")})),e.up&&this.controlsUp.forEach((e=>{e.classList.add("enabled"),e.removeAttribute("disabled")})),e.down&&this.controlsDown.forEach((e=>{e.classList.add("enabled"),e.removeAttribute("disabled")})),(e.left||e.up)&&this.controlsPrev.forEach((e=>{e.classList.add("enabled"),e.removeAttribute("disabled")})),(e.right||e.down)&&this.controlsNext.forEach((e=>{e.classList.add("enabled"),e.removeAttribute("disabled")}));let t=this.Reveal.getCurrentSlide();if(t){let e=this.Reveal.fragments.availableRoutes();e.prev&&this.controlsPrev.forEach((e=>{e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")})),e.next&&this.controlsNext.forEach((e=>{e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")})),this.Reveal.isVerticalSlide(t)?(e.prev&&this.controlsUp.forEach((e=>{e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")})),e.next&&this.controlsDown.forEach((e=>{e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")}))):(e.prev&&this.controlsLeft.forEach((e=>{e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")})),e.next&&this.controlsRight.forEach((e=>{e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")})))}if(this.Reveal.getConfig().controlsTutorial){let t=this.Reveal.getIndices();!this.Reveal.hasNavigatedVertically()&&e.down?this.controlsDownArrow.classList.add("highlight"):(this.controlsDownArrow.classList.remove("highlight"),this.Reveal.getConfig().rtl?!this.Reveal.hasNavigatedHorizontally()&&e.left&&0===t.v?this.controlsLeftArrow.classList.add("highlight"):this.controlsLeftArrow.classList.remove("highlight"):!this.Reveal.hasNavigatedHorizontally()&&e.right&&0===t.v?this.controlsRightArrow.classList.add("highlight"):this.controlsRightArrow.classList.remove("highlight"))}}destroy(){this.unbind(),this.element.remove()}onNavigateLeftClicked(e){e.preventDefault(),this.Reveal.onUserInput(),"linear"===this.Reveal.getConfig().navigationMode?this.Reveal.prev():this.Reveal.left()}onNavigateRightClicked(e){e.preventDefault(),this.Reveal.onUserInput(),"linear"===this.Reveal.getConfig().navigationMode?this.Reveal.next():this.Reveal.right()}onNavigateUpClicked(e){e.preventDefault(),this.Reveal.onUserInput(),this.Reveal.up()}onNavigateDownClicked(e){e.preventDefault(),this.Reveal.onUserInput(),this.Reveal.down()}onNavigatePrevClicked(e){e.preventDefault(),this.Reveal.onUserInput(),this.Reveal.prev()}onNavigateNextClicked(e){e.preventDefault(),this.Reveal.onUserInput(),this.Reveal.next()}}class M{constructor(e){this.Reveal=e,this.onProgressClicked=this.onProgressClicked.bind(this)}render(){this.element=document.createElement("div"),this.element.className="progress",this.Reveal.getRevealElement().appendChild(this.element),this.bar=document.createElement("span"),this.element.appendChild(this.bar)}configure(e,t){this.element.style.display=e.progress?"block":"none"}bind(){this.Reveal.getConfig().progress&&this.element&&this.element.addEventListener("click",this.onProgressClicked,!1)}unbind(){this.Reveal.getConfig().progress&&this.element&&this.element.removeEventListener("click",this.onProgressClicked,!1)}update(){if(this.Reveal.getConfig().progress&&this.bar){let e=this.Reveal.getProgress();this.Reveal.getTotalSlides()<2&&(e=0),this.bar.style.transform="scaleX("+e+")"}}getMaxWidth(){return this.Reveal.getRevealElement().offsetWidth}onProgressClicked(e){this.Reveal.onUserInput(e),e.preventDefault();let t=this.Reveal.getSlides(),i=t.length,s=Math.floor(e.clientX/this.getMaxWidth()*i);this.Reveal.getConfig().rtl&&(s=i-s);let a=this.Reveal.getIndices(t[s]);this.Reveal.slide(a.h,a.v)}destroy(){this.element.remove()}}class D{constructor(e){this.Reveal=e,this.lastMouseWheelStep=0,this.cursorHidden=!1,this.cursorInactiveTimeout=0,this.onDocumentCursorActive=this.onDocumentCursorActive.bind(this),this.onDocumentMouseScroll=this.onDocumentMouseScroll.bind(this)}configure(e,t){e.mouseWheel?(document.addEventListener("DOMMouseScroll",this.onDocumentMouseScroll,!1),document.addEventListener("mousewheel",this.onDocumentMouseScroll,!1)):(document.removeEventListener("DOMMouseScroll",this.onDocumentMouseScroll,!1),document.removeEventListener("mousewheel",this.onDocumentMouseScroll,!1)),e.hideInactiveCursor?(document.addEventListener("mousemove",this.onDocumentCursorActive,!1),document.addEventListener("mousedown",this.onDocumentCursorActive,!1)):(this.showCursor(),document.removeEventListener("mousemove",this.onDocumentCursorActive,!1),document.removeEventListener("mousedown",this.onDocumentCursorActive,!1))}showCursor(){this.cursorHidden&&(this.cursorHidden=!1,this.Reveal.getRevealElement().style.cursor="")}hideCursor(){!1===this.cursorHidden&&(this.cursorHidden=!0,this.Reveal.getRevealElement().style.cursor="none")}destroy(){this.showCursor(),document.removeEventListener("DOMMouseScroll",this.onDocumentMouseScroll,!1),document.removeEventListener("mousewheel",this.onDocumentMouseScroll,!1),document.removeEventListener("mousemove",this.onDocumentCursorActive,!1),document.removeEventListener("mousedown",this.onDocumentCursorActive,!1)}onDocumentCursorActive(e){this.showCursor(),clearTimeout(this.cursorInactiveTimeout),this.cursorInactiveTimeout=setTimeout(this.hideCursor.bind(this),this.Reveal.getConfig().hideCursorTime)}onDocumentMouseScroll(e){if(Date.now()-this.lastMouseWheelStep>1e3){this.lastMouseWheelStep=Date.now();let t=e.detail||-e.wheelDelta;t>0?this.Reveal.next():t<0&&this.Reveal.prev()}}}const I=(e,t)=>{const i=document.createElement("script");i.type="text/javascript",i.async=!1,i.defer=!1,i.src=e,"function"==typeof t&&(i.onload=i.onreadystatechange=e=>{("load"===e.type||/loaded|complete/.test(i.readyState))&&(i.onload=i.onreadystatechange=i.onerror=null,t())},i.onerror=e=>{i.onload=i.onreadystatechange=i.onerror=null,t(new Error("Failed loading script: "+i.src+"\n"+e))});const s=document.querySelector("head");s.insertBefore(i,s.lastChild)};class T{constructor(e){this.Reveal=e,this.state="idle",this.registeredPlugins={},this.asyncDependencies=[]}load(e,t){return this.state="loading",e.forEach(this.registerPlugin.bind(this)),new Promise((e=>{let i=[],s=0;if(t.forEach((e=>{e.condition&&!e.condition()||(e.async?this.asyncDependencies.push(e):i.push(e))})),i.length){s=i.length;const t=t=>{t&&"function"==typeof t.callback&&t.callback(),0==--s&&this.initPlugins().then(e)};i.forEach((e=>{"string"==typeof e.id?(this.registerPlugin(e),t(e)):"string"==typeof e.src?I(e.src,(()=>t(e))):(console.warn("Unrecognized plugin format",e),t())}))}else this.initPlugins().then(e)}))}initPlugins(){return new Promise((e=>{let t=Object.values(this.registeredPlugins),i=t.length;if(0===i)this.loadAsync().then(e);else{let s,a=()=>{0==--i?this.loadAsync().then(e):s()},n=0;s=()=>{let e=t[n++];if("function"==typeof e.init){let t=e.init(this.Reveal);t&&"function"==typeof t.then?t.then(a):a()}else a()},s()}}))}loadAsync(){return this.state="loaded",this.asyncDependencies.length&&this.asyncDependencies.forEach((e=>{I(e.src,e.callback)})),Promise.resolve()}registerPlugin(e){2===arguments.length&&"string"==typeof arguments[0]?(e=arguments[1]).id=arguments[0]:"function"==typeof e&&(e=e());let t=e.id;"string"!=typeof t?console.warn("Unrecognized plugin format; can't find plugin.id",e):void 0===this.registeredPlugins[t]?(this.registeredPlugins[t]=e,"loaded"===this.state&&"function"==typeof e.init&&e.init(this.Reveal)):console.warn('reveal.js: "'+t+'" plugin has already been registered')}hasPlugin(e){return!!this.registeredPlugins[e]}getPlugin(e){return this.registeredPlugins[e]}getRegisteredPlugins(){return this.registeredPlugins}destroy(){Object.values(this.registeredPlugins).forEach((e=>{"function"==typeof e.destroy&&e.destroy()})),this.registeredPlugins={},this.asyncDependencies=[]}}class F{constructor(e){this.Reveal=e}async setupPDF(){const e=this.Reveal.getConfig(),i=t(this.Reveal.getRevealElement(),".slides section"),s=e.slideNumber&&/all|print/i.test(e.showSlideNumber),a=this.Reveal.getComputedSlideSize(window.innerWidth,window.innerHeight),n=Math.floor(a.width*(1+e.margin)),r=Math.floor(a.height*(1+e.margin)),o=a.width,d=a.height;await new Promise(requestAnimationFrame),l("@page{size:"+n+"px "+r+"px; margin: 0px;}"),l(".reveal section>img, .reveal section>video, .reveal section>iframe{max-width: "+o+"px; max-height:"+d+"px}"),document.documentElement.classList.add("print-pdf"),document.body.style.width=n+"px",document.body.style.height=r+"px";const c=document.querySelector(".reveal-viewport");let h;if(c){const e=window.getComputedStyle(c);e&&e.background&&(h=e.background)}await new Promise(requestAnimationFrame),this.Reveal.layoutSlideContents(o,d),await new Promise(requestAnimationFrame);const u=i.map((e=>e.scrollHeight)),g=[],v=i[0].parentNode;i.forEach((function(i,a){if(!1===i.classList.contains("stack")){let l=(n-o)/2,c=(r-d)/2;const v=u[a];let p=Math.max(Math.ceil(v/r),1);p=Math.min(p,e.pdfMaxPagesPerSlide),(1===p&&e.center||i.classList.contains("center"))&&(c=Math.max((r-v)/2,0));const m=document.createElement("div");if(g.push(m),m.className="pdf-page",m.style.height=(r+e.pdfPageHeightOffset)*p+"px",h&&(m.style.background=h),m.appendChild(i),i.style.left=l+"px",i.style.top=c+"px",i.style.width=o+"px",this.Reveal.slideContent.layout(i),i.slideBackgroundElement&&m.insertBefore(i.slideBackgroundElement,i),e.showNotes){const t=this.Reveal.getSlideNotes(i);if(t){const i=8,s="string"==typeof e.showNotes?e.showNotes:"inline",a=document.createElement("div");a.classList.add("speaker-notes"),a.classList.add("speaker-notes-pdf"),a.setAttribute("data-layout",s),a.innerHTML=t,"separate-page"===s?g.push(a):(a.style.left=i+"px",a.style.bottom=i+"px",a.style.width=n-2*i+"px",m.appendChild(a))}}if(s){const e=a+1,t=document.createElement("div");t.classList.add("slide-number"),t.classList.add("slide-number-pdf"),t.innerHTML=e,m.appendChild(t)}if(e.pdfSeparateFragments){const e=this.Reveal.fragments.sort(m.querySelectorAll(".fragment"),!0);let t;e.forEach((function(e){t&&t.forEach((function(e){e.classList.remove("current-fragment")})),e.forEach((function(e){e.classList.add("visible","current-fragment")}),this);const i=m.cloneNode(!0);g.push(i),t=e}),this),e.forEach((function(e){e.forEach((function(e){e.classList.remove("visible","current-fragment")}))}))}else t(m,".fragment:not(.fade-out)").forEach((function(e){e.classList.add("visible")}))}}),this),await new Promise(requestAnimationFrame),g.forEach((e=>v.appendChild(e))),this.Reveal.dispatchEvent({type:"pdf-ready"})}isPrintingPDF(){return/print-pdf/gi.test(window.location.search)}}class z{constructor(e){this.Reveal=e,this.touchStartX=0,this.touchStartY=0,this.touchStartCount=0,this.touchCaptured=!1,this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),this.onTouchStart=this.onTouchStart.bind(this),this.onTouchMove=this.onTouchMove.bind(this),this.onTouchEnd=this.onTouchEnd.bind(this)}bind(){let e=this.Reveal.getRevealElement();"onpointerdown"in window?(e.addEventListener("pointerdown",this.onPointerDown,!1),e.addEventListener("pointermove",this.onPointerMove,!1),e.addEventListener("pointerup",this.onPointerUp,!1)):window.navigator.msPointerEnabled?(e.addEventListener("MSPointerDown",this.onPointerDown,!1),e.addEventListener("MSPointerMove",this.onPointerMove,!1),e.addEventListener("MSPointerUp",this.onPointerUp,!1)):(e.addEventListener("touchstart",this.onTouchStart,!1),e.addEventListener("touchmove",this.onTouchMove,!1),e.addEventListener("touchend",this.onTouchEnd,!1))}unbind(){let e=this.Reveal.getRevealElement();e.removeEventListener("pointerdown",this.onPointerDown,!1),e.removeEventListener("pointermove",this.onPointerMove,!1),e.removeEventListener("pointerup",this.onPointerUp,!1),e.removeEventListener("MSPointerDown",this.onPointerDown,!1),e.removeEventListener("MSPointerMove",this.onPointerMove,!1),e.removeEventListener("MSPointerUp",this.onPointerUp,!1),e.removeEventListener("touchstart",this.onTouchStart,!1),e.removeEventListener("touchmove",this.onTouchMove,!1),e.removeEventListener("touchend",this.onTouchEnd,!1)}isSwipePrevented(e){if(n(e,"video, audio"))return!0;for(;e&&"function"==typeof e.hasAttribute;){if(e.hasAttribute("data-prevent-swipe"))return!0;e=e.parentNode}return!1}onTouchStart(e){if(this.isSwipePrevented(e.target))return!0;this.touchStartX=e.touches[0].clientX,this.touchStartY=e.touches[0].clientY,this.touchStartCount=e.touches.length}onTouchMove(e){if(this.isSwipePrevented(e.target))return!0;let t=this.Reveal.getConfig();if(this.touchCaptured)v&&e.preventDefault();else{this.Reveal.onUserInput(e);let i=e.touches[0].clientX,s=e.touches[0].clientY;if(1===e.touches.length&&2!==this.touchStartCount){let a=this.Reveal.availableRoutes({includeFragments:!0}),n=i-this.touchStartX,r=s-this.touchStartY;n>40&&Math.abs(n)>Math.abs(r)?(this.touchCaptured=!0,"linear"===t.navigationMode?t.rtl?this.Reveal.next():this.Reveal.prev():this.Reveal.left()):n<-40&&Math.abs(n)>Math.abs(r)?(this.touchCaptured=!0,"linear"===t.navigationMode?t.rtl?this.Reveal.prev():this.Reveal.next():this.Reveal.right()):r>40&&a.up?(this.touchCaptured=!0,"linear"===t.navigationMode?this.Reveal.prev():this.Reveal.up()):r<-40&&a.down&&(this.touchCaptured=!0,"linear"===t.navigationMode?this.Reveal.next():this.Reveal.down()),t.embedded?(this.touchCaptured||this.Reveal.isVerticalSlide())&&e.preventDefault():e.preventDefault()}}}onTouchEnd(e){this.touchCaptured=!1}onPointerDown(e){e.pointerType!==e.MSPOINTER_TYPE_TOUCH&&"touch"!==e.pointerType||(e.touches=[{clientX:e.clientX,clientY:e.clientY}],this.onTouchStart(e))}onPointerMove(e){e.pointerType!==e.MSPOINTER_TYPE_TOUCH&&"touch"!==e.pointerType||(e.touches=[{clientX:e.clientX,clientY:e.clientY}],this.onTouchMove(e))}onPointerUp(e){e.pointerType!==e.MSPOINTER_TYPE_TOUCH&&"touch"!==e.pointerType||(e.touches=[{clientX:e.clientX,clientY:e.clientY}],this.onTouchEnd(e))}}class H{constructor(e){this.Reveal=e,this.onRevealPointerDown=this.onRevealPointerDown.bind(this),this.onDocumentPointerDown=this.onDocumentPointerDown.bind(this)}configure(e,t){e.embedded?this.blur():(this.focus(),this.unbind())}bind(){this.Reveal.getConfig().embedded&&this.Reveal.getRevealElement().addEventListener("pointerdown",this.onRevealPointerDown,!1)}unbind(){this.Reveal.getRevealElement().removeEventListener("pointerdown",this.onRevealPointerDown,!1),document.removeEventListener("pointerdown",this.onDocumentPointerDown,!1)}focus(){"focus"!==this.state&&(this.Reveal.getRevealElement().classList.add("focused"),document.addEventListener("pointerdown",this.onDocumentPointerDown,!1)),this.state="focus"}blur(){"blur"!==this.state&&(this.Reveal.getRevealElement().classList.remove("focused"),document.removeEventListener("pointerdown",this.onDocumentPointerDown,!1)),this.state="blur"}isFocused(){return"focus"===this.state}destroy(){this.Reveal.getRevealElement().classList.remove("focused")}onRevealPointerDown(e){this.focus()}onDocumentPointerDown(e){let t=r(e.target,".reveal");t&&t===this.Reveal.getRevealElement()||this.blur()}}class q{constructor(e){this.Reveal=e}render(){this.element=document.createElement("div"),this.element.className="speaker-notes",this.element.setAttribute("data-prevent-swipe",""),this.element.setAttribute("tabindex","0"),this.Reveal.getRevealElement().appendChild(this.element)}configure(e,t){e.showNotes&&this.element.setAttribute("data-layout","string"==typeof e.showNotes?e.showNotes:"inline")}update(){this.Reveal.getConfig().showNotes&&this.element&&this.Reveal.getCurrentSlide()&&!this.Reveal.print.isPrintingPDF()&&(this.element.innerHTML=this.getSlideNotes()||'No notes on this slide.')}updateVisibility(){this.Reveal.getConfig().showNotes&&this.hasNotes()&&!this.Reveal.print.isPrintingPDF()?this.Reveal.getRevealElement().classList.add("show-notes"):this.Reveal.getRevealElement().classList.remove("show-notes")}hasNotes(){return this.Reveal.getSlidesElement().querySelectorAll("[data-notes], aside.notes").length>0}isSpeakerNotesWindow(){return!!window.location.search.match(/receiver/gi)}getSlideNotes(e=this.Reveal.getCurrentSlide()){if(e.hasAttribute("data-notes"))return e.getAttribute("data-notes");let t=e.querySelector("aside.notes");return t?t.innerHTML:null}destroy(){this.element.remove()}}class B{constructor(e,t){this.diameter=100,this.diameter2=this.diameter/2,this.thickness=6,this.playing=!1,this.progress=0,this.progressOffset=1,this.container=e,this.progressCheck=t,this.canvas=document.createElement("canvas"),this.canvas.className="playback",this.canvas.width=this.diameter,this.canvas.height=this.diameter,this.canvas.style.width=this.diameter2+"px",this.canvas.style.height=this.diameter2+"px",this.context=this.canvas.getContext("2d"),this.container.appendChild(this.canvas),this.render()}setPlaying(e){const t=this.playing;this.playing=e,!t&&this.playing?this.animate():this.render()}animate(){const e=this.progress;this.progress=this.progressCheck(),e>.8&&this.progress<.2&&(this.progressOffset=this.progress),this.render(),this.playing&&requestAnimationFrame(this.animate.bind(this))}render(){let e=this.playing?this.progress:0,t=this.diameter2-this.thickness,i=this.diameter2,s=this.diameter2,a=28;this.progressOffset+=.1*(1-this.progressOffset);const n=-Math.PI/2+e*(2*Math.PI),r=-Math.PI/2+this.progressOffset*(2*Math.PI);this.context.save(),this.context.clearRect(0,0,this.diameter,this.diameter),this.context.beginPath(),this.context.arc(i,s,t+4,0,2*Math.PI,!1),this.context.fillStyle="rgba( 0, 0, 0, 0.4 )",this.context.fill(),this.context.beginPath(),this.context.arc(i,s,t,0,2*Math.PI,!1),this.context.lineWidth=this.thickness,this.context.strokeStyle="rgba( 255, 255, 255, 0.2 )",this.context.stroke(),this.playing&&(this.context.beginPath(),this.context.arc(i,s,t,r,n,!1),this.context.lineWidth=this.thickness,this.context.strokeStyle="#fff",this.context.stroke()),this.context.translate(i-14,s-14),this.playing?(this.context.fillStyle="#fff",this.context.fillRect(0,0,10,a),this.context.fillRect(18,0,10,a)):(this.context.beginPath(),this.context.translate(4,0),this.context.moveTo(0,0),this.context.lineTo(24,14),this.context.lineTo(0,a),this.context.fillStyle="#fff",this.context.fill()),this.context.restore()}on(e,t){this.canvas.addEventListener(e,t,!1)}off(e,t){this.canvas.removeEventListener(e,t,!1)}destroy(){this.playing=!1,this.canvas.parentNode&&this.container.removeChild(this.canvas)}}var O={width:960,height:700,margin:.04,minScale:.2,maxScale:2,controls:!0,controlsTutorial:!0,controlsLayout:"bottom-right",controlsBackArrows:"faded",progress:!0,slideNumber:!1,showSlideNumber:"all",hashOneBasedIndex:!1,hash:!1,respondToHashChanges:!0,history:!1,keyboard:!0,keyboardCondition:null,disableLayout:!1,overview:!0,center:!0,touch:!0,loop:!1,rtl:!1,navigationMode:"default",shuffle:!1,fragments:!0,fragmentInURL:!0,embedded:!1,help:!0,pause:!0,showNotes:!1,showHiddenSlides:!1,autoPlayMedia:null,preloadIframes:null,autoAnimate:!0,autoAnimateMatcher:null,autoAnimateEasing:"ease",autoAnimateDuration:1,autoAnimateUnmatched:!0,autoAnimateStyles:["opacity","color","background-color","padding","font-size","line-height","letter-spacing","border-width","border-color","border-radius","outline","outline-offset"],autoSlide:0,autoSlideStoppable:!0,autoSlideMethod:null,defaultTiming:null,mouseWheel:!1,previewLinks:!1,postMessage:!0,postMessageEvents:!1,focusBodyOnPageVisibilityChange:!0,transition:"slide",transitionSpeed:"default",backgroundTransition:"fade",parallaxBackgroundImage:"",parallaxBackgroundSize:"",parallaxBackgroundRepeat:"",parallaxBackgroundPosition:"",parallaxBackgroundHorizontal:null,parallaxBackgroundVertical:null,pdfMaxPagesPerSlide:Number.POSITIVE_INFINITY,pdfSeparateFragments:!0,pdfPageHeightOffset:-1,viewDistance:3,mobileViewDistance:2,display:"block",hideInactiveCursor:!0,hideCursorTime:5e3,dependencies:[],plugins:[]};function U(n,l){arguments.length<2&&(l=arguments[0],n=document.querySelector(".reveal"));const h={};let u,v,p,m,f,w={},S=!1,A={hasNavigatedHorizontally:!1,hasNavigatedVertically:!1},I=[],U=1,W={layout:"",overview:""},K={},V="idle",$=0,j=0,X=-1,Y=!1,_=new b(h),J=new y(h),Q=new k(h),Z=new E(h),G=new L(h),ee=new C(h),te=new x(h),ie=new P(h),se=new N(h),ae=new M(h),ne=new D(h),re=new T(h),oe=new F(h),le=new H(h),de=new z(h),ce=new q(h);function he(e){if(!n)throw'Unable to find presentation root (
    ).';if(K.wrapper=n,K.slides=n.querySelector(".slides"),!K.slides)throw'Unable to find slides container (
    ).';return w={...O,...w,...l,...e,...d()},ue(),window.addEventListener("load",He,!1),re.load(w.plugins,w.dependencies).then(ge),new Promise((e=>h.on("ready",e)))}function ue(){!0===w.embedded?K.viewport=r(n,".reveal-viewport")||n:(K.viewport=document.body,document.documentElement.classList.add("reveal-full-page")),K.viewport.classList.add("reveal-viewport")}function ge(){S=!0,ve(),pe(),Ee(),ye(),we(),tt(),Re(),ie.readURL(),Z.update(!0),setTimeout((()=>{K.slides.classList.remove("no-transition"),K.wrapper.classList.add("ready"),Pe({type:"ready",data:{indexh:u,indexv:v,currentSlide:m}})}),1),oe.isPrintingPDF()&&(Ae(),"complete"===document.readyState?oe.setupPDF():window.addEventListener("load",(()=>{oe.setupPDF()})))}function ve(){w.showHiddenSlides||t(K.wrapper,'section[data-visibility="hidden"]').forEach((e=>{e.parentNode.removeChild(e)}))}function pe(){K.slides.classList.add("no-transition"),g?K.wrapper.classList.add("no-hover"):K.wrapper.classList.remove("no-hover"),Z.render(),J.render(),se.render(),ae.render(),ce.render(),K.pauseOverlay=o(K.wrapper,"div","pause-overlay",w.controls?'':null),K.statusElement=me(),K.wrapper.setAttribute("role","application")}function me(){let e=K.wrapper.querySelector(".aria-status");return e||(e=document.createElement("div"),e.style.position="absolute",e.style.height="1px",e.style.width="1px",e.style.overflow="hidden",e.style.clip="rect( 1px, 1px, 1px, 1px )",e.classList.add("aria-status"),e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","true"),K.wrapper.appendChild(e)),e}function fe(e){K.statusElement.textContent=e}function be(e){let t="";if(3===e.nodeType)t+=e.textContent;else if(1===e.nodeType){let i=e.getAttribute("aria-hidden"),s="none"===window.getComputedStyle(e).display;"true"===i||s||Array.from(e.childNodes).forEach((e=>{t+=be(e)}))}return t=t.trim(),""===t?"":t+" "}function ye(){setInterval((()=>{0===K.wrapper.scrollTop&&0===K.wrapper.scrollLeft||(K.wrapper.scrollTop=0,K.wrapper.scrollLeft=0)}),1e3)}function we(){document.addEventListener("fullscreenchange",Ht),document.addEventListener("webkitfullscreenchange",Ht)}function Ee(){w.postMessage&&window.addEventListener("message",Dt,!1)}function Re(t){const s={...w};if("object"==typeof t&&e(w,t),!1===h.isReady())return;const a=K.wrapper.querySelectorAll(".slides section").length;K.wrapper.classList.remove(s.transition),K.wrapper.classList.add(w.transition),K.wrapper.setAttribute("data-transition-speed",w.transitionSpeed),K.wrapper.setAttribute("data-background-transition",w.backgroundTransition),K.viewport.style.setProperty("--slide-width",w.width+"px"),K.viewport.style.setProperty("--slide-height",w.height+"px"),w.shuffle&&it(),i(K.wrapper,"embedded",w.embedded),i(K.wrapper,"rtl",w.rtl),i(K.wrapper,"center",w.center),!1===w.pause&&Xe(),w.previewLinks?(Me(),De("[data-preview-link=false]")):(De(),Me("[data-preview-link]:not([data-preview-link=false])")),Q.reset(),f&&(f.destroy(),f=null),a>1&&w.autoSlide&&w.autoSlideStoppable&&(f=new B(K.wrapper,(()=>Math.min(Math.max((Date.now()-X)/$,0),1))),f.on("click",Bt),Y=!1),"default"!==w.navigationMode?K.wrapper.setAttribute("data-navigation-mode",w.navigationMode):K.wrapper.removeAttribute("data-navigation-mode"),ce.configure(w,s),le.configure(w,s),ne.configure(w,s),se.configure(w,s),ae.configure(w,s),te.configure(w,s),G.configure(w,s),J.configure(w,s),Ge()}function Se(){window.addEventListener("resize",Ft,!1),w.touch&&de.bind(),w.keyboard&&te.bind(),w.progress&&ae.bind(),w.respondToHashChanges&&ie.bind(),se.bind(),le.bind(),K.slides.addEventListener("click",Tt,!1),K.slides.addEventListener("transitionend",It,!1),K.pauseOverlay.addEventListener("click",Xe,!1),w.focusBodyOnPageVisibilityChange&&document.addEventListener("visibilitychange",zt,!1)}function Ae(){de.unbind(),le.unbind(),te.unbind(),se.unbind(),ae.unbind(),ie.unbind(),window.removeEventListener("resize",Ft,!1),K.slides.removeEventListener("click",Tt,!1),K.slides.removeEventListener("transitionend",It,!1),K.pauseOverlay.removeEventListener("click",Xe,!1)}function ke(){Ae(),Rt(),De(),ce.destroy(),le.destroy(),re.destroy(),ne.destroy(),se.destroy(),ae.destroy(),Z.destroy(),J.destroy(),document.removeEventListener("fullscreenchange",Ht),document.removeEventListener("webkitfullscreenchange",Ht),document.removeEventListener("visibilitychange",zt,!1),window.removeEventListener("message",Dt,!1),window.removeEventListener("load",He,!1),K.pauseOverlay&&K.pauseOverlay.remove(),K.statusElement&&K.statusElement.remove(),document.documentElement.classList.remove("reveal-full-page"),K.wrapper.classList.remove("ready","center","has-horizontal-slides","has-vertical-slides"),K.wrapper.removeAttribute("data-transition-speed"),K.wrapper.removeAttribute("data-background-transition"),K.viewport.classList.remove("reveal-viewport"),K.viewport.style.removeProperty("--slide-width"),K.viewport.style.removeProperty("--slide-height"),K.slides.style.removeProperty("width"),K.slides.style.removeProperty("height"),K.slides.style.removeProperty("zoom"),K.slides.style.removeProperty("left"),K.slides.style.removeProperty("top"),K.slides.style.removeProperty("bottom"),K.slides.style.removeProperty("right"),K.slides.style.removeProperty("transform"),Array.from(K.wrapper.querySelectorAll(".slides section")).forEach((e=>{e.style.removeProperty("display"),e.style.removeProperty("top"),e.removeAttribute("hidden"),e.removeAttribute("aria-hidden")}))}function Le(e,t,i){n.addEventListener(e,t,i)}function Ce(e,t,i){n.removeEventListener(e,t,i)}function xe(e){"string"==typeof e.layout&&(W.layout=e.layout),"string"==typeof e.overview&&(W.overview=e.overview),W.layout?a(K.slides,W.layout+" "+W.overview):a(K.slides,W.overview)}function Pe({target:t=K.wrapper,type:i,data:s,bubbles:a=!0}){let n=document.createEvent("HTMLEvents",1,2);return n.initEvent(i,a,!0),e(n,s),t.dispatchEvent(n),t===K.wrapper&&Ne(i),n}function Ne(t,i){if(w.postMessageEvents&&window.parent!==window.self){let s={namespace:"reveal",eventName:t,state:yt()};e(s,i),window.parent.postMessage(JSON.stringify(s),"*")}}function Me(e="a"){Array.from(K.wrapper.querySelectorAll(e)).forEach((e=>{/^(http|www)/gi.test(e.getAttribute("href"))&&e.addEventListener("click",qt,!1)}))}function De(e="a"){Array.from(K.wrapper.querySelectorAll(e)).forEach((e=>{/^(http|www)/gi.test(e.getAttribute("href"))&&e.removeEventListener("click",qt,!1)}))}function Ie(e){ze(),K.overlay=document.createElement("div"),K.overlay.classList.add("overlay"),K.overlay.classList.add("overlay-preview"),K.wrapper.appendChild(K.overlay),K.overlay.innerHTML=`
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t
    \n\t\t\t
    \n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tUnable to load iframe. This is likely due to the site's policy (x-frame-options).\n\t\t\t\t\n\t\t\t
    `,K.overlay.querySelector("iframe").addEventListener("load",(e=>{K.overlay.classList.add("loaded")}),!1),K.overlay.querySelector(".close").addEventListener("click",(e=>{ze(),e.preventDefault()}),!1),K.overlay.querySelector(".external").addEventListener("click",(e=>{ze()}),!1)}function Te(e){"boolean"==typeof e?e?Fe():ze():K.overlay?ze():Fe()}function Fe(){if(w.help){ze(),K.overlay=document.createElement("div"),K.overlay.classList.add("overlay"),K.overlay.classList.add("overlay-help"),K.wrapper.appendChild(K.overlay);let e='

    Keyboard Shortcuts


    ',t=te.getShortcuts(),i=te.getBindings();e+="
    ";for(let i in t)e+=``;for(let t in i)i[t].key&&i[t].description&&(e+=``);e+="
    KEYACTION
    ${i}${t[i]}
    ${i[t].key}${i[t].description}
    ",K.overlay.innerHTML=`\n\t\t\t\t
    \n\t\t\t\t\t\n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t\t
    ${e}
    \n\t\t\t\t
    \n\t\t\t`,K.overlay.querySelector(".close").addEventListener("click",(e=>{ze(),e.preventDefault()}),!1)}}function ze(){return!!K.overlay&&(K.overlay.parentNode.removeChild(K.overlay),K.overlay=null,!0)}function He(){if(K.wrapper&&!oe.isPrintingPDF()){if(!w.disableLayout){g&&!w.embedded&&document.documentElement.style.setProperty("--vh",.01*window.innerHeight+"px");const e=Be(),t=U;qe(w.width,w.height),K.slides.style.width=e.width+"px",K.slides.style.height=e.height+"px",U=Math.min(e.presentationWidth/e.width,e.presentationHeight/e.height),U=Math.max(U,w.minScale),U=Math.min(U,w.maxScale),1===U?(K.slides.style.zoom="",K.slides.style.left="",K.slides.style.top="",K.slides.style.bottom="",K.slides.style.right="",xe({layout:""})):(K.slides.style.zoom="",K.slides.style.left="50%",K.slides.style.top="50%",K.slides.style.bottom="auto",K.slides.style.right="auto",xe({layout:"translate(-50%, -50%) scale("+U+")"}));const i=Array.from(K.wrapper.querySelectorAll(".slides section"));for(let t=0,s=i.length;t .stretch, section > .r-stretch").forEach((t=>{let s=c(t,i);if(/(img|video)/gi.test(t.nodeName)){const i=t.naturalWidth||t.videoWidth,a=t.naturalHeight||t.videoHeight,n=Math.min(e/i,s/a);t.style.width=i*n+"px",t.style.height=a*n+"px"}else t.style.width=e+"px",t.style.height=s+"px"}))}function Be(e,t){const i={width:w.width,height:w.height,presentationWidth:e||K.wrapper.offsetWidth,presentationHeight:t||K.wrapper.offsetHeight};return i.presentationWidth-=i.presentationWidth*w.margin,i.presentationHeight-=i.presentationHeight*w.margin,"string"==typeof i.width&&/%$/.test(i.width)&&(i.width=parseInt(i.width,10)/100*i.presentationWidth),"string"==typeof i.height&&/%$/.test(i.height)&&(i.height=parseInt(i.height,10)/100*i.presentationHeight),i}function Oe(e,t){"object"==typeof e&&"function"==typeof e.setAttribute&&e.setAttribute("data-previous-indexv",t||0)}function Ue(e){if("object"==typeof e&&"function"==typeof e.setAttribute&&e.classList.contains("stack")){const t=e.hasAttribute("data-start-indexv")?"data-start-indexv":"data-previous-indexv";return parseInt(e.getAttribute(t)||0,10)}return 0}function We(e=m){return e&&e.parentNode&&!!e.parentNode.nodeName.match(/section/i)}function Ke(){return!(!m||!We(m))&&!m.nextElementSibling}function Ve(){return 0===u&&0===v}function $e(){return!!m&&(!m.nextElementSibling&&(!We(m)||!m.parentNode.nextElementSibling))}function je(){if(w.pause){const e=K.wrapper.classList.contains("paused");Rt(),K.wrapper.classList.add("paused"),!1===e&&Pe({type:"paused"})}}function Xe(){const e=K.wrapper.classList.contains("paused");K.wrapper.classList.remove("paused"),Et(),e&&Pe({type:"resumed"})}function Ye(e){"boolean"==typeof e?e?je():Xe():_e()?Xe():je()}function _e(){return K.wrapper.classList.contains("paused")}function Je(e){"boolean"==typeof e?e?At():St():Y?At():St()}function Qe(){return!(!$||Y)}function Ze(e,t,i,s){if(Pe({type:"beforeslidechange",data:{indexh:void 0===e?u:e,indexv:void 0===t?v:t,origin:s}}).defaultPrevented)return;p=m;const a=K.wrapper.querySelectorAll(".slides>section");if(0===a.length)return;void 0!==t||ee.isActive()||(t=Ue(a[e])),p&&p.parentNode&&p.parentNode.classList.contains("stack")&&Oe(p.parentNode,v);const n=I.concat();I.length=0;let r=u||0,o=v||0;u=st(".slides>section",void 0===e?u:e),v=st(".slides>section.present>section",void 0===t?v:t);let l=u!==r||v!==o;l||(p=null);let d=a[u],c=d.querySelectorAll("section");m=c[v]||d;let h=!1;l&&p&&m&&!ee.isActive()&&(p.hasAttribute("data-auto-animate")&&m.hasAttribute("data-auto-animate")&&p.getAttribute("data-auto-animate-id")===m.getAttribute("data-auto-animate-id")&&!(u>r||v>o?m:p).hasAttribute("data-auto-animate-restart")&&(h=!0,K.slides.classList.add("disable-slide-transitions")),V="running"),at(),He(),ee.isActive()&&ee.update(),void 0!==i&&G.goto(i),p&&p!==m&&(p.classList.remove("present"),p.setAttribute("aria-hidden","true"),Ve()&&setTimeout((()=>{ut().forEach((e=>{Oe(e,0)}))}),0));e:for(let e=0,t=I.length;e{fe(be(m))})),ae.update(),se.update(),ce.update(),Z.update(),Z.updateParallax(),J.update(),G.update(),ie.writeURL(),Et(),h&&(setTimeout((()=>{K.slides.classList.remove("disable-slide-transitions")}),0),w.autoAnimate&&Q.run(p,m))}function Ge(){Ae(),Se(),He(),$=w.autoSlide,Et(),Z.create(),ie.writeURL(),G.sortAll(),se.update(),ae.update(),at(),ce.update(),ce.updateVisibility(),Z.update(!0),J.update(),_.formatEmbeddedContent(),!1===w.autoPlayMedia?_.stopEmbeddedContent(m,{unloadIframes:!1}):_.startEmbeddedContent(m),ee.isActive()&&ee.layout()}function et(e=m){Z.sync(e),G.sync(e),_.load(e),Z.update(),ce.update()}function tt(){ct().forEach((e=>{t(e,"section").forEach(((e,t)=>{t>0&&(e.classList.remove("present"),e.classList.remove("past"),e.classList.add("future"),e.setAttribute("aria-hidden","true"))}))}))}function it(e=ct()){e.forEach(((t,i)=>{let s=e[Math.floor(Math.random()*e.length)];s.parentNode===t.parentNode&&t.parentNode.insertBefore(t,s);let a=t.querySelectorAll("section");a.length&&it(a)}))}function st(e,i){let s=t(K.wrapper,e),a=s.length,n=oe.isPrintingPDF();if(a){w.loop&&(i%=a)<0&&(i=a+i),i=Math.max(Math.min(i,a-1),0);for(let e=0;e{e.classList.add("visible"),e.classList.remove("current-fragment")}))):e>i&&(a.classList.add(r?"past":"future"),w.fragments&&t(a,".fragment.visible").forEach((e=>{e.classList.remove("visible","current-fragment")})))}let e=s[i],r=e.classList.contains("present");e.classList.add("present"),e.removeAttribute("hidden"),e.removeAttribute("aria-hidden"),r||Pe({target:e,type:"visible",bubbles:!1});let o=e.getAttribute("data-state");o&&(I=I.concat(o.split(" ")))}else i=0;return i}function at(){let e,i,s=ct(),a=s.length;if(a&&void 0!==u){let n=ee.isActive()?10:w.viewDistance;g&&(n=ee.isActive()?6:w.mobileViewDistance),oe.isPrintingPDF()&&(n=Number.MAX_VALUE);for(let r=0;rsection"),i=K.wrapper.querySelectorAll(".slides>section.present>section"),s={left:u>0,right:u0,down:v1&&(s.left=!0,s.right=!0),i.length>1&&(s.up=!0,s.down=!0)),t.length>1&&"linear"===w.navigationMode&&(s.right=s.right||s.down,s.left=s.left||s.up),!0===e){let e=G.availableRoutes();s.left=s.left||e.prev,s.up=s.up||e.prev,s.down=s.down||e.next,s.right=s.right||e.next}if(w.rtl){let e=s.left;s.left=s.right,s.right=e}return s}function rt(e=m){let t=ct(),i=0;e:for(let s=0;s0){let i=.9;t+=m.querySelectorAll(".fragment.visible").length/e.length*i}}return Math.min(t/(e-1),1)}function lt(e){let i,s=u,a=v;if(e){let i=We(e),n=i?e.parentNode:e,r=ct();s=Math.max(r.indexOf(n),0),a=void 0,i&&(a=Math.max(t(e.parentNode,"section").indexOf(e),0))}if(!e&&m){if(m.querySelectorAll(".fragment").length>0){let e=m.querySelector(".current-fragment");i=e&&e.hasAttribute("data-fragment-index")?parseInt(e.getAttribute("data-fragment-index"),10):m.querySelectorAll(".fragment.visible").length-1}}return{h:s,v:a,f:i}}function dt(){return t(K.wrapper,'.slides section:not(.stack):not([data-visibility="uncounted"])')}function ct(){return t(K.wrapper,".slides>section")}function ht(){return t(K.wrapper,".slides>section>section")}function ut(){return t(K.wrapper,".slides>section.stack")}function gt(){return ct().length>1}function vt(){return ht().length>1}function pt(){return dt().map((e=>{let t={};for(let i=0;i{e.hasAttribute("data-autoplay")&&$&&1e3*e.duration/e.playbackRate>$&&($=1e3*e.duration/e.playbackRate+1e3)}))),!$||Y||_e()||ee.isActive()||$e()&&!G.availableRoutes().next&&!0!==w.loop||(j=setTimeout((()=>{"function"==typeof w.autoSlideMethod?w.autoSlideMethod():Nt(),Et()}),$),X=Date.now()),f&&f.setPlaying(-1!==j)}}function Rt(){clearTimeout(j),j=-1}function St(){$&&!Y&&(Y=!0,Pe({type:"autoslidepaused"}),clearTimeout(j),f&&f.setPlaying(!1))}function At(){$&&Y&&(Y=!1,Pe({type:"autoslideresumed"}),Et())}function kt({skipFragments:e=!1}={}){A.hasNavigatedHorizontally=!0,w.rtl?(ee.isActive()||e||!1===G.next())&&nt().left&&Ze(u+1,"grid"===w.navigationMode?v:void 0):(ee.isActive()||e||!1===G.prev())&&nt().left&&Ze(u-1,"grid"===w.navigationMode?v:void 0)}function Lt({skipFragments:e=!1}={}){A.hasNavigatedHorizontally=!0,w.rtl?(ee.isActive()||e||!1===G.prev())&&nt().right&&Ze(u-1,"grid"===w.navigationMode?v:void 0):(ee.isActive()||e||!1===G.next())&&nt().right&&Ze(u+1,"grid"===w.navigationMode?v:void 0)}function Ct({skipFragments:e=!1}={}){(ee.isActive()||e||!1===G.prev())&&nt().up&&Ze(u,v-1)}function xt({skipFragments:e=!1}={}){A.hasNavigatedVertically=!0,(ee.isActive()||e||!1===G.next())&&nt().down&&Ze(u,v+1)}function Pt({skipFragments:e=!1}={}){if(e||!1===G.prev())if(nt().up)Ct({skipFragments:e});else{let i;if(i=w.rtl?t(K.wrapper,".slides>section.future").pop():t(K.wrapper,".slides>section.past").pop(),i&&i.classList.contains("stack")){let e=i.querySelectorAll("section").length-1||void 0;Ze(u-1,e)}else kt({skipFragments:e})}}function Nt({skipFragments:e=!1}={}){if(A.hasNavigatedHorizontally=!0,A.hasNavigatedVertically=!0,e||!1===G.next()){let t=nt();t.down&&t.right&&w.loop&&Ke()&&(t.down=!1),t.down?xt({skipFragments:e}):w.rtl?kt({skipFragments:e}):Lt({skipFragments:e})}}function Mt(e){w.autoSlideStoppable&&St()}function Dt(e){let t=e.data;if("string"==typeof t&&"{"===t.charAt(0)&&"}"===t.charAt(t.length-1)&&(t=JSON.parse(t),t.method&&"function"==typeof h[t.method]))if(!1===R.test(t.method)){const e=h[t.method].apply(h,t.args);Ne("callback",{method:t.method,result:e})}else console.warn('reveal.js: "'+t.method+'" is is blacklisted from the postMessage API')}function It(e){"running"===V&&/section/gi.test(e.target.nodeName)&&(V="idle",Pe({type:"slidetransitionend",data:{indexh:u,indexv:v,previousSlide:p,currentSlide:m}}))}function Tt(e){const t=r(e.target,'a[href^="#"]');if(t){const i=t.getAttribute("href"),s=ie.getIndicesFromHash(i);s&&(h.slide(s.h,s.v,s.f),e.preventDefault())}}function Ft(e){He()}function zt(e){!1===document.hidden&&document.activeElement!==document.body&&("function"==typeof document.activeElement.blur&&document.activeElement.blur(),document.body.focus())}function Ht(e){(document.fullscreenElement||document.webkitFullscreenElement)===K.wrapper&&(e.stopImmediatePropagation(),setTimeout((()=>{h.layout(),h.focus.focus()}),1))}function qt(e){if(e.currentTarget&&e.currentTarget.hasAttribute("href")){let t=e.currentTarget.getAttribute("href");t&&(Ie(t),e.preventDefault())}}function Bt(e){$e()&&!1===w.loop?(Ze(0,0),At()):Y?At():St()}const Ot={VERSION:"4.3.1",initialize:he,configure:Re,destroy:ke,sync:Ge,syncSlide:et,syncFragments:G.sync.bind(G),slide:Ze,left:kt,right:Lt,up:Ct,down:xt,prev:Pt,next:Nt,navigateLeft:kt,navigateRight:Lt,navigateUp:Ct,navigateDown:xt,navigatePrev:Pt,navigateNext:Nt,navigateFragment:G.goto.bind(G),prevFragment:G.prev.bind(G),nextFragment:G.next.bind(G),on:Le,off:Ce,addEventListener:Le,removeEventListener:Ce,layout:He,shuffle:it,availableRoutes:nt,availableFragments:G.availableRoutes.bind(G),toggleHelp:Te,toggleOverview:ee.toggle.bind(ee),togglePause:Ye,toggleAutoSlide:Je,isFirstSlide:Ve,isLastSlide:$e,isLastVerticalSlide:Ke,isVerticalSlide:We,isPaused:_e,isAutoSliding:Qe,isSpeakerNotes:ce.isSpeakerNotesWindow.bind(ce),isOverview:ee.isActive.bind(ee),isFocused:le.isFocused.bind(le),isPrintingPDF:oe.isPrintingPDF.bind(oe),isReady:()=>S,loadSlide:_.load.bind(_),unloadSlide:_.unload.bind(_),showPreview:Ie,hidePreview:ze,addEventListeners:Se,removeEventListeners:Ae,dispatchEvent:Pe,getState:yt,setState:wt,getProgress:ot,getIndices:lt,getSlidesAttributes:pt,getSlidePastCount:rt,getTotalSlides:mt,getSlide:ft,getPreviousSlide:()=>p,getCurrentSlide:()=>m,getSlideBackground:bt,getSlideNotes:ce.getSlideNotes.bind(ce),getSlides:dt,getHorizontalSlides:ct,getVerticalSlides:ht,hasHorizontalSlides:gt,hasVerticalSlides:vt,hasNavigatedHorizontally:()=>A.hasNavigatedHorizontally,hasNavigatedVertically:()=>A.hasNavigatedVertically,addKeyBinding:te.addKeyBinding.bind(te),removeKeyBinding:te.removeKeyBinding.bind(te),triggerKey:te.triggerKey.bind(te),registerKeyboardShortcut:te.registerKeyboardShortcut.bind(te),getComputedSlideSize:Be,getScale:()=>U,getConfig:()=>w,getQueryHash:d,getSlidePath:ie.getHash.bind(ie),getRevealElement:()=>n,getSlidesElement:()=>K.slides,getViewportElement:()=>K.viewport,getBackgroundsElement:()=>Z.element,registerPlugin:re.registerPlugin.bind(re),hasPlugin:re.hasPlugin.bind(re),getPlugin:re.getPlugin.bind(re),getPlugins:re.getRegisteredPlugins.bind(re)};return e(h,{...Ot,announceStatus:fe,getStatusText:be,print:oe,focus:le,progress:ae,controls:se,location:ie,overview:ee,fragments:G,slideContent:_,slideNumber:J,onUserInput:Mt,closeOverlay:ze,updateSlidesVisibility:at,layoutSlideContents:qe,transformSlides:xe,cueAutoSlide:Et,cancelAutoSlide:Rt}),Ot}let W=U,K=[];W.initialize=e=>(Object.assign(W,new U(document.querySelector(".reveal"),e)),K.map((e=>e(W))),W.initialize()),["configure","on","off","addEventListener","removeEventListener","registerPlugin"].forEach((e=>{W[e]=(...t)=>{K.push((i=>i[e].call(null,...t)))}})),W.isReady=()=>!1,W.VERSION="4.3.1";export default W; -//# sourceMappingURL=reveal.esm.js.map diff --git a/choosing_files/libs/revealjs/dist/reveal.esm.js.map b/choosing_files/libs/revealjs/dist/reveal.esm.js.map deleted file mode 100644 index 286c75a..0000000 --- a/choosing_files/libs/revealjs/dist/reveal.esm.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"reveal.esm.js","sources":["../js/utils/util.js","../js/utils/device.js","../node_modules/fitty/dist/fitty.module.js","../js/controllers/slidecontent.js","../js/controllers/slidenumber.js","../js/utils/color.js","../js/controllers/backgrounds.js","../js/utils/constants.js","../js/controllers/autoanimate.js","../js/controllers/fragments.js","../js/controllers/overview.js","../js/controllers/keyboard.js","../js/controllers/location.js","../js/controllers/controls.js","../js/controllers/progress.js","../js/controllers/pointer.js","../js/utils/loader.js","../js/controllers/plugins.js","../js/controllers/print.js","../js/controllers/touch.js","../js/controllers/focus.js","../js/controllers/notes.js","../js/components/playback.js","../js/config.js","../js/reveal.js","../js/index.js"],"sourcesContent":["/**\n * Extend object a with the properties of object b.\n * If there's a conflict, object b takes precedence.\n *\n * @param {object} a\n * @param {object} b\n */\nexport const extend = ( a, b ) => {\n\n\tfor( let i in b ) {\n\t\ta[ i ] = b[ i ];\n\t}\n\n\treturn a;\n\n}\n\n/**\n * querySelectorAll but returns an Array.\n */\nexport const queryAll = ( el, selector ) => {\n\n\treturn Array.from( el.querySelectorAll( selector ) );\n\n}\n\n/**\n * classList.toggle() with cross browser support\n */\nexport const toggleClass = ( el, className, value ) => {\n\tif( value ) {\n\t\tel.classList.add( className );\n\t}\n\telse {\n\t\tel.classList.remove( className );\n\t}\n}\n\n/**\n * Utility for deserializing a value.\n *\n * @param {*} value\n * @return {*}\n */\nexport const deserialize = ( value ) => {\n\n\tif( typeof value === 'string' ) {\n\t\tif( value === 'null' ) return null;\n\t\telse if( value === 'true' ) return true;\n\t\telse if( value === 'false' ) return false;\n\t\telse if( value.match( /^-?[\\d\\.]+$/ ) ) return parseFloat( value );\n\t}\n\n\treturn value;\n\n}\n\n/**\n * Measures the distance in pixels between point a\n * and point b.\n *\n * @param {object} a point with x/y properties\n * @param {object} b point with x/y properties\n *\n * @return {number}\n */\nexport const distanceBetween = ( a, b ) => {\n\n\tlet dx = a.x - b.x,\n\t\tdy = a.y - b.y;\n\n\treturn Math.sqrt( dx*dx + dy*dy );\n\n}\n\n/**\n * Applies a CSS transform to the target element.\n *\n * @param {HTMLElement} element\n * @param {string} transform\n */\nexport const transformElement = ( element, transform ) => {\n\n\telement.style.transform = transform;\n\n}\n\n/**\n * Element.matches with IE support.\n *\n * @param {HTMLElement} target The element to match\n * @param {String} selector The CSS selector to match\n * the element against\n *\n * @return {Boolean}\n */\nexport const matches = ( target, selector ) => {\n\n\tlet matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector;\n\n\treturn !!( matchesMethod && matchesMethod.call( target, selector ) );\n\n}\n\n/**\n * Find the closest parent that matches the given\n * selector.\n *\n * @param {HTMLElement} target The child element\n * @param {String} selector The CSS selector to match\n * the parents against\n *\n * @return {HTMLElement} The matched parent or null\n * if no matching parent was found\n */\nexport const closest = ( target, selector ) => {\n\n\t// Native Element.closest\n\tif( typeof target.closest === 'function' ) {\n\t\treturn target.closest( selector );\n\t}\n\n\t// Polyfill\n\twhile( target ) {\n\t\tif( matches( target, selector ) ) {\n\t\t\treturn target;\n\t\t}\n\n\t\t// Keep searching\n\t\ttarget = target.parentNode;\n\t}\n\n\treturn null;\n\n}\n\n/**\n * Handling the fullscreen functionality via the fullscreen API\n *\n * @see http://fullscreen.spec.whatwg.org/\n * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode\n */\nexport const enterFullscreen = element => {\n\n\telement = element || document.documentElement;\n\n\t// Check which implementation is available\n\tlet requestMethod = element.requestFullscreen ||\n\t\t\t\t\t\telement.webkitRequestFullscreen ||\n\t\t\t\t\t\telement.webkitRequestFullScreen ||\n\t\t\t\t\t\telement.mozRequestFullScreen ||\n\t\t\t\t\t\telement.msRequestFullscreen;\n\n\tif( requestMethod ) {\n\t\trequestMethod.apply( element );\n\t}\n\n}\n\n/**\n * Creates an HTML element and returns a reference to it.\n * If the element already exists the existing instance will\n * be returned.\n *\n * @param {HTMLElement} container\n * @param {string} tagname\n * @param {string} classname\n * @param {string} innerHTML\n *\n * @return {HTMLElement}\n */\nexport const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => {\n\n\t// Find all nodes matching the description\n\tlet nodes = container.querySelectorAll( '.' + classname );\n\n\t// Check all matches to find one which is a direct child of\n\t// the specified container\n\tfor( let i = 0; i < nodes.length; i++ ) {\n\t\tlet testNode = nodes[i];\n\t\tif( testNode.parentNode === container ) {\n\t\t\treturn testNode;\n\t\t}\n\t}\n\n\t// If no node was found, create it now\n\tlet node = document.createElement( tagname );\n\tnode.className = classname;\n\tnode.innerHTML = innerHTML;\n\tcontainer.appendChild( node );\n\n\treturn node;\n\n}\n\n/**\n * Injects the given CSS styles into the DOM.\n *\n * @param {string} value\n */\nexport const createStyleSheet = ( value ) => {\n\n\tlet tag = document.createElement( 'style' );\n\ttag.type = 'text/css';\n\n\tif( value && value.length > 0 ) {\n\t\tif( tag.styleSheet ) {\n\t\t\ttag.styleSheet.cssText = value;\n\t\t}\n\t\telse {\n\t\t\ttag.appendChild( document.createTextNode( value ) );\n\t\t}\n\t}\n\n\tdocument.head.appendChild( tag );\n\n\treturn tag;\n\n}\n\n/**\n * Returns a key:value hash of all query params.\n */\nexport const getQueryHash = () => {\n\n\tlet query = {};\n\n\tlocation.search.replace( /[A-Z0-9]+?=([\\w\\.%-]*)/gi, a => {\n\t\tquery[ a.split( '=' ).shift() ] = a.split( '=' ).pop();\n\t} );\n\n\t// Basic deserialization\n\tfor( let i in query ) {\n\t\tlet value = query[ i ];\n\n\t\tquery[ i ] = deserialize( unescape( value ) );\n\t}\n\n\t// Do not accept new dependencies via query config to avoid\n\t// the potential of malicious script injection\n\tif( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];\n\n\treturn query;\n\n}\n\n/**\n * Returns the remaining height within the parent of the\n * target element.\n *\n * remaining height = [ configured parent height ] - [ current parent height ]\n *\n * @param {HTMLElement} element\n * @param {number} [height]\n */\nexport const getRemainingHeight = ( element, height = 0 ) => {\n\n\tif( element ) {\n\t\tlet newHeight, oldHeight = element.style.height;\n\n\t\t// Change the .stretch element height to 0 in order find the height of all\n\t\t// the other elements\n\t\telement.style.height = '0px';\n\n\t\t// In Overview mode, the parent (.slide) height is set of 700px.\n\t\t// Restore it temporarily to its natural height.\n\t\telement.parentNode.style.height = 'auto';\n\n\t\tnewHeight = height - element.parentNode.offsetHeight;\n\n\t\t// Restore the old height, just in case\n\t\telement.style.height = oldHeight + 'px';\n\n\t\t// Clear the parent (.slide) height. .removeProperty works in IE9+\n\t\telement.parentNode.style.removeProperty('height');\n\n\t\treturn newHeight;\n\t}\n\n\treturn height;\n\n}\n\nconst fileExtensionToMimeMap = {\n\t'mp4': 'video/mp4',\n\t'm4a': 'video/mp4',\n\t'ogv': 'video/ogg',\n\t'mpeg': 'video/mpeg',\n\t'webm': 'video/webm'\n}\n\n/**\n * Guess the MIME type for common file formats.\n */\nexport const getMimeTypeFromFile = ( filename='' ) => {\n\treturn fileExtensionToMimeMap[filename.split('.').pop()]\n}","const UA = navigator.userAgent;\n\nexport const isMobile = /(iphone|ipod|ipad|android)/gi.test( UA ) ||\n\t\t\t\t\t\t( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS\n\nexport const isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );\n\nexport const isAndroid = /android/gi.test( UA );","/*\n * fitty v2.3.3 - Snugly resizes text to fit its parent container\n * Copyright (c) 2020 Rik Schennink (https://pqina.nl/)\n */\n'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nexports.default = function (w) {\n\n // no window, early exit\n if (!w) return;\n\n // node list to array helper method\n var toArray = function toArray(nl) {\n return [].slice.call(nl);\n };\n\n // states\n var DrawState = {\n IDLE: 0,\n DIRTY_CONTENT: 1,\n DIRTY_LAYOUT: 2,\n DIRTY: 3\n };\n\n // all active fitty elements\n var fitties = [];\n\n // group all redraw calls till next frame, we cancel each frame request when a new one comes in. If no support for request animation frame, this is an empty function and supports for fitty stops.\n var redrawFrame = null;\n var requestRedraw = 'requestAnimationFrame' in w ? function () {\n w.cancelAnimationFrame(redrawFrame);\n redrawFrame = w.requestAnimationFrame(function () {\n return redraw(fitties.filter(function (f) {\n return f.dirty && f.active;\n }));\n });\n } : function () {};\n\n // sets all fitties to dirty so they are redrawn on the next redraw loop, then calls redraw\n var redrawAll = function redrawAll(type) {\n return function () {\n fitties.forEach(function (f) {\n return f.dirty = type;\n });\n requestRedraw();\n };\n };\n\n // redraws fitties so they nicely fit their parent container\n var redraw = function redraw(fitties) {\n\n // getting info from the DOM at this point should not trigger a reflow, let's gather as much intel as possible before triggering a reflow\n\n // check if styles of all fitties have been computed\n fitties.filter(function (f) {\n return !f.styleComputed;\n }).forEach(function (f) {\n f.styleComputed = computeStyle(f);\n });\n\n // restyle elements that require pre-styling, this triggers a reflow, please try to prevent by adding CSS rules (see docs)\n fitties.filter(shouldPreStyle).forEach(applyStyle);\n\n // we now determine which fitties should be redrawn\n var fittiesToRedraw = fitties.filter(shouldRedraw);\n\n // we calculate final styles for these fitties\n fittiesToRedraw.forEach(calculateStyles);\n\n // now we apply the calculated styles from our previous loop\n fittiesToRedraw.forEach(function (f) {\n applyStyle(f);\n markAsClean(f);\n });\n\n // now we dispatch events for all restyled fitties\n fittiesToRedraw.forEach(dispatchFitEvent);\n };\n\n var markAsClean = function markAsClean(f) {\n return f.dirty = DrawState.IDLE;\n };\n\n var calculateStyles = function calculateStyles(f) {\n\n // get available width from parent node\n f.availableWidth = f.element.parentNode.clientWidth;\n\n // the space our target element uses\n f.currentWidth = f.element.scrollWidth;\n\n // remember current font size\n f.previousFontSize = f.currentFontSize;\n\n // let's calculate the new font size\n f.currentFontSize = Math.min(Math.max(f.minSize, f.availableWidth / f.currentWidth * f.previousFontSize), f.maxSize);\n\n // if allows wrapping, only wrap when at minimum font size (otherwise would break container)\n f.whiteSpace = f.multiLine && f.currentFontSize === f.minSize ? 'normal' : 'nowrap';\n };\n\n // should always redraw if is not dirty layout, if is dirty layout, only redraw if size has changed\n var shouldRedraw = function shouldRedraw(f) {\n return f.dirty !== DrawState.DIRTY_LAYOUT || f.dirty === DrawState.DIRTY_LAYOUT && f.element.parentNode.clientWidth !== f.availableWidth;\n };\n\n // every fitty element is tested for invalid styles\n var computeStyle = function computeStyle(f) {\n\n // get style properties\n var style = w.getComputedStyle(f.element, null);\n\n // get current font size in pixels (if we already calculated it, use the calculated version)\n f.currentFontSize = parseFloat(style.getPropertyValue('font-size'));\n\n // get display type and wrap mode\n f.display = style.getPropertyValue('display');\n f.whiteSpace = style.getPropertyValue('white-space');\n };\n\n // determines if this fitty requires initial styling, can be prevented by applying correct styles through CSS\n var shouldPreStyle = function shouldPreStyle(f) {\n\n var preStyle = false;\n\n // if we already tested for prestyling we don't have to do it again\n if (f.preStyleTestCompleted) return false;\n\n // should have an inline style, if not, apply\n if (!/inline-/.test(f.display)) {\n preStyle = true;\n f.display = 'inline-block';\n }\n\n // to correctly calculate dimensions the element should have whiteSpace set to nowrap\n if (f.whiteSpace !== 'nowrap') {\n preStyle = true;\n f.whiteSpace = 'nowrap';\n }\n\n // we don't have to do this twice\n f.preStyleTestCompleted = true;\n\n return preStyle;\n };\n\n // apply styles to single fitty\n var applyStyle = function applyStyle(f) {\n f.element.style.whiteSpace = f.whiteSpace;\n f.element.style.display = f.display;\n f.element.style.fontSize = f.currentFontSize + 'px';\n };\n\n // dispatch a fit event on a fitty\n var dispatchFitEvent = function dispatchFitEvent(f) {\n f.element.dispatchEvent(new CustomEvent('fit', {\n detail: {\n oldValue: f.previousFontSize,\n newValue: f.currentFontSize,\n scaleFactor: f.currentFontSize / f.previousFontSize\n }\n }));\n };\n\n // fit method, marks the fitty as dirty and requests a redraw (this will also redraw any other fitty marked as dirty)\n var fit = function fit(f, type) {\n return function () {\n f.dirty = type;\n if (!f.active) return;\n requestRedraw();\n };\n };\n\n var init = function init(f) {\n\n // save some of the original CSS properties before we change them\n f.originalStyle = {\n whiteSpace: f.element.style.whiteSpace,\n display: f.element.style.display,\n fontSize: f.element.style.fontSize\n };\n\n // should we observe DOM mutations\n observeMutations(f);\n\n // this is a new fitty so we need to validate if it's styles are in order\n f.newbie = true;\n\n // because it's a new fitty it should also be dirty, we want it to redraw on the first loop\n f.dirty = true;\n\n // we want to be able to update this fitty\n fitties.push(f);\n };\n\n var destroy = function destroy(f) {\n return function () {\n\n // remove from fitties array\n fitties = fitties.filter(function (_) {\n return _.element !== f.element;\n });\n\n // stop observing DOM\n if (f.observeMutations) f.observer.disconnect();\n\n // reset the CSS properties we changes\n f.element.style.whiteSpace = f.originalStyle.whiteSpace;\n f.element.style.display = f.originalStyle.display;\n f.element.style.fontSize = f.originalStyle.fontSize;\n };\n };\n\n // add a new fitty, does not redraw said fitty\n var subscribe = function subscribe(f) {\n return function () {\n if (f.active) return;\n f.active = true;\n requestRedraw();\n };\n };\n\n // remove an existing fitty\n var unsubscribe = function unsubscribe(f) {\n return function () {\n return f.active = false;\n };\n };\n\n var observeMutations = function observeMutations(f) {\n\n // no observing?\n if (!f.observeMutations) return;\n\n // start observing mutations\n f.observer = new MutationObserver(fit(f, DrawState.DIRTY_CONTENT));\n\n // start observing\n f.observer.observe(f.element, f.observeMutations);\n };\n\n // default mutation observer settings\n var mutationObserverDefaultSetting = {\n subtree: true,\n childList: true,\n characterData: true\n };\n\n // default fitty options\n var defaultOptions = {\n minSize: 16,\n maxSize: 512,\n multiLine: true,\n observeMutations: 'MutationObserver' in w ? mutationObserverDefaultSetting : false\n };\n\n // array of elements in, fitty instances out\n function fittyCreate(elements, options) {\n\n // set options object\n var fittyOptions = _extends({}, defaultOptions, options);\n\n // create fitties\n var publicFitties = elements.map(function (element) {\n\n // create fitty instance\n var f = _extends({}, fittyOptions, {\n\n // internal options for this fitty\n element: element,\n active: true\n });\n\n // initialise this fitty\n init(f);\n\n // expose API\n return {\n element: element,\n fit: fit(f, DrawState.DIRTY),\n unfreeze: subscribe(f),\n freeze: unsubscribe(f),\n unsubscribe: destroy(f)\n };\n });\n\n // call redraw on newly initiated fitties\n requestRedraw();\n\n // expose fitties\n return publicFitties;\n }\n\n // fitty creation function\n function fitty(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n\n\n // if target is a string\n return typeof target === 'string' ?\n\n // treat it as a querySelector\n fittyCreate(toArray(document.querySelectorAll(target)), options) :\n\n // create single fitty\n fittyCreate([target], options)[0];\n }\n\n // handles viewport changes, redraws all fitties, but only does so after a timeout\n var resizeDebounce = null;\n var onWindowResized = function onWindowResized() {\n w.clearTimeout(resizeDebounce);\n resizeDebounce = w.setTimeout(redrawAll(DrawState.DIRTY_LAYOUT), fitty.observeWindowDelay);\n };\n\n // define observe window property, so when we set it to true or false events are automatically added and removed\n var events = ['resize', 'orientationchange'];\n Object.defineProperty(fitty, 'observeWindow', {\n set: function set(enabled) {\n var method = (enabled ? 'add' : 'remove') + 'EventListener';\n events.forEach(function (e) {\n w[method](e, onWindowResized);\n });\n }\n });\n\n // fitty global properties (by setting observeWindow to true the events above get added)\n fitty.observeWindow = true;\n fitty.observeWindowDelay = 100;\n\n // public fit all method, will force redraw no matter what\n fitty.fitAll = redrawAll(DrawState.DIRTY);\n\n // export our fitty function, we don't want to keep it to our selves\n return fitty;\n}(typeof window === 'undefined' ? null : window);","import { extend, queryAll, closest, getMimeTypeFromFile } from '../utils/util.js'\nimport { isMobile } from '../utils/device.js'\n\nimport fitty from 'fitty';\n\n/**\n * Handles loading, unloading and playback of slide\n * content such as images, videos and iframes.\n */\nexport default class SlideContent {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\tthis.startEmbeddedIframe = this.startEmbeddedIframe.bind( this );\n\n\t}\n\n\t/**\n\t * Should the given element be preloaded?\n\t * Decides based on local element attributes and global config.\n\t *\n\t * @param {HTMLElement} element\n\t */\n\tshouldPreload( element ) {\n\n\t\t// Prefer an explicit global preload setting\n\t\tlet preload = this.Reveal.getConfig().preloadIframes;\n\n\t\t// If no global setting is available, fall back on the element's\n\t\t// own preload setting\n\t\tif( typeof preload !== 'boolean' ) {\n\t\t\tpreload = element.hasAttribute( 'data-preload' );\n\t\t}\n\n\t\treturn preload;\n\t}\n\n\t/**\n\t * Called when the given slide is within the configured view\n\t * distance. Shows the slide element and loads any content\n\t * that is set to load lazily (data-src).\n\t *\n\t * @param {HTMLElement} slide Slide to show\n\t */\n\tload( slide, options = {} ) {\n\n\t\t// Show the slide element\n\t\tslide.style.display = this.Reveal.getConfig().display;\n\n\t\t// Media elements with data-src attributes\n\t\tqueryAll( slide, 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ).forEach( element => {\n\t\t\tif( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {\n\t\t\t\telement.setAttribute( 'src', element.getAttribute( 'data-src' ) );\n\t\t\t\telement.setAttribute( 'data-lazy-loaded', '' );\n\t\t\t\telement.removeAttribute( 'data-src' );\n\t\t\t}\n\t\t} );\n\n\t\t// Media elements with children\n\t\tqueryAll( slide, 'video, audio' ).forEach( media => {\n\t\t\tlet sources = 0;\n\n\t\t\tqueryAll( media, 'source[data-src]' ).forEach( source => {\n\t\t\t\tsource.setAttribute( 'src', source.getAttribute( 'data-src' ) );\n\t\t\t\tsource.removeAttribute( 'data-src' );\n\t\t\t\tsource.setAttribute( 'data-lazy-loaded', '' );\n\t\t\t\tsources += 1;\n\t\t\t} );\n\n\t\t\t// Enable inline video playback in mobile Safari\n\t\t\tif( isMobile && media.tagName === 'VIDEO' ) {\n\t\t\t\tmedia.setAttribute( 'playsinline', '' );\n\t\t\t}\n\n\t\t\t// If we rewrote sources for this video/audio element, we need\n\t\t\t// to manually tell it to load from its new origin\n\t\t\tif( sources > 0 ) {\n\t\t\t\tmedia.load();\n\t\t\t}\n\t\t} );\n\n\n\t\t// Show the corresponding background element\n\t\tlet background = slide.slideBackgroundElement;\n\t\tif( background ) {\n\t\t\tbackground.style.display = 'block';\n\n\t\t\tlet backgroundContent = slide.slideBackgroundContentElement;\n\t\t\tlet backgroundIframe = slide.getAttribute( 'data-background-iframe' );\n\n\t\t\t// If the background contains media, load it\n\t\t\tif( background.hasAttribute( 'data-loaded' ) === false ) {\n\t\t\t\tbackground.setAttribute( 'data-loaded', 'true' );\n\n\t\t\t\tlet backgroundImage = slide.getAttribute( 'data-background-image' ),\n\t\t\t\t\tbackgroundVideo = slide.getAttribute( 'data-background-video' ),\n\t\t\t\t\tbackgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),\n\t\t\t\t\tbackgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' );\n\n\t\t\t\t// Images\n\t\t\t\tif( backgroundImage ) {\n\t\t\t\t\t// base64\n\t\t\t\t\tif( /^data:/.test( backgroundImage.trim() ) ) {\n\t\t\t\t\t\tbackgroundContent.style.backgroundImage = `url(${backgroundImage.trim()})`;\n\t\t\t\t\t}\n\t\t\t\t\t// URL(s)\n\t\t\t\t\telse {\n\t\t\t\t\t\tbackgroundContent.style.backgroundImage = backgroundImage.split( ',' ).map( background => {\n\t\t\t\t\t\t\treturn `url(${encodeURI(background.trim())})`;\n\t\t\t\t\t\t}).join( ',' );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Videos\n\t\t\t\telse if ( backgroundVideo && !this.Reveal.isSpeakerNotes() ) {\n\t\t\t\t\tlet video = document.createElement( 'video' );\n\n\t\t\t\t\tif( backgroundVideoLoop ) {\n\t\t\t\t\t\tvideo.setAttribute( 'loop', '' );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( backgroundVideoMuted ) {\n\t\t\t\t\t\tvideo.muted = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Enable inline playback in mobile Safari\n\t\t\t\t\t//\n\t\t\t\t\t// Mute is required for video to play when using\n\t\t\t\t\t// swipe gestures to navigate since they don't\n\t\t\t\t\t// count as direct user actions :'(\n\t\t\t\t\tif( isMobile ) {\n\t\t\t\t\t\tvideo.muted = true;\n\t\t\t\t\t\tvideo.setAttribute( 'playsinline', '' );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support comma separated lists of video sources\n\t\t\t\t\tbackgroundVideo.split( ',' ).forEach( source => {\n\t\t\t\t\t\tlet type = getMimeTypeFromFile( source );\n\t\t\t\t\t\tif( type ) {\n\t\t\t\t\t\t\tvideo.innerHTML += ``;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tvideo.innerHTML += ``;\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\n\t\t\t\t\tbackgroundContent.appendChild( video );\n\t\t\t\t}\n\t\t\t\t// Iframes\n\t\t\t\telse if( backgroundIframe && options.excludeIframes !== true ) {\n\t\t\t\t\tlet iframe = document.createElement( 'iframe' );\n\t\t\t\t\tiframe.setAttribute( 'allowfullscreen', '' );\n\t\t\t\t\tiframe.setAttribute( 'mozallowfullscreen', '' );\n\t\t\t\t\tiframe.setAttribute( 'webkitallowfullscreen', '' );\n\t\t\t\t\tiframe.setAttribute( 'allow', 'autoplay' );\n\n\t\t\t\t\tiframe.setAttribute( 'data-src', backgroundIframe );\n\n\t\t\t\t\tiframe.style.width = '100%';\n\t\t\t\t\tiframe.style.height = '100%';\n\t\t\t\t\tiframe.style.maxHeight = '100%';\n\t\t\t\t\tiframe.style.maxWidth = '100%';\n\n\t\t\t\t\tbackgroundContent.appendChild( iframe );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Start loading preloadable iframes\n\t\t\tlet backgroundIframeElement = backgroundContent.querySelector( 'iframe[data-src]' );\n\t\t\tif( backgroundIframeElement ) {\n\n\t\t\t\t// Check if this iframe is eligible to be preloaded\n\t\t\t\tif( this.shouldPreload( background ) && !/autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {\n\t\t\t\t\tif( backgroundIframeElement.getAttribute( 'src' ) !== backgroundIframe ) {\n\t\t\t\t\t\tbackgroundIframeElement.setAttribute( 'src', backgroundIframe );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\tthis.layout( slide );\n\n\t}\n\n\t/**\n\t * Applies JS-dependent layout helpers for the given slide,\n\t * if there are any.\n\t */\n\tlayout( slide ) {\n\n\t\t// Autosize text with the r-fit-text class based on the\n\t\t// size of its container. This needs to happen after the\n\t\t// slide is visible in order to measure the text.\n\t\tArray.from( slide.querySelectorAll( '.r-fit-text' ) ).forEach( element => {\n\t\t\tfitty( element, {\n\t\t\t\tminSize: 24,\n\t\t\t\tmaxSize: this.Reveal.getConfig().height * 0.8,\n\t\t\t\tobserveMutations: false,\n\t\t\t\tobserveWindow: false\n\t\t\t} );\n\t\t} );\n\n\t}\n\n\t/**\n\t * Unloads and hides the given slide. This is called when the\n\t * slide is moved outside of the configured view distance.\n\t *\n\t * @param {HTMLElement} slide\n\t */\n\tunload( slide ) {\n\n\t\t// Hide the slide element\n\t\tslide.style.display = 'none';\n\n\t\t// Hide the corresponding background element\n\t\tlet background = this.Reveal.getSlideBackground( slide );\n\t\tif( background ) {\n\t\t\tbackground.style.display = 'none';\n\n\t\t\t// Unload any background iframes\n\t\t\tqueryAll( background, 'iframe[src]' ).forEach( element => {\n\t\t\t\telement.removeAttribute( 'src' );\n\t\t\t} );\n\t\t}\n\n\t\t// Reset lazy-loaded media elements with src attributes\n\t\tqueryAll( slide, 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src], iframe[data-lazy-loaded][src]' ).forEach( element => {\n\t\t\telement.setAttribute( 'data-src', element.getAttribute( 'src' ) );\n\t\t\telement.removeAttribute( 'src' );\n\t\t} );\n\n\t\t// Reset lazy-loaded media elements with children\n\t\tqueryAll( slide, 'video[data-lazy-loaded] source[src], audio source[src]' ).forEach( source => {\n\t\t\tsource.setAttribute( 'data-src', source.getAttribute( 'src' ) );\n\t\t\tsource.removeAttribute( 'src' );\n\t\t} );\n\n\t}\n\n\t/**\n\t * Enforces origin-specific format rules for embedded media.\n\t */\n\tformatEmbeddedContent() {\n\n\t\tlet _appendParamToIframeSource = ( sourceAttribute, sourceURL, param ) => {\n\t\t\tqueryAll( this.Reveal.getSlidesElement(), 'iframe['+ sourceAttribute +'*=\"'+ sourceURL +'\"]' ).forEach( el => {\n\t\t\t\tlet src = el.getAttribute( sourceAttribute );\n\t\t\t\tif( src && src.indexOf( param ) === -1 ) {\n\t\t\t\t\tel.setAttribute( sourceAttribute, src + ( !/\\?/.test( src ) ? '?' : '&' ) + param );\n\t\t\t\t}\n\t\t\t});\n\t\t};\n\n\t\t// YouTube frames must include \"?enablejsapi=1\"\n\t\t_appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );\n\t\t_appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );\n\n\t\t// Vimeo frames must include \"?api=1\"\n\t\t_appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );\n\t\t_appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );\n\n\t}\n\n\t/**\n\t * Start playback of any embedded content inside of\n\t * the given element.\n\t *\n\t * @param {HTMLElement} element\n\t */\n\tstartEmbeddedContent( element ) {\n\n\t\tif( element && !this.Reveal.isSpeakerNotes() ) {\n\n\t\t\t// Restart GIFs\n\t\t\tqueryAll( element, 'img[src$=\".gif\"]' ).forEach( el => {\n\t\t\t\t// Setting the same unchanged source like this was confirmed\n\t\t\t\t// to work in Chrome, FF & Safari\n\t\t\t\tel.setAttribute( 'src', el.getAttribute( 'src' ) );\n\t\t\t} );\n\n\t\t\t// HTML5 media elements\n\t\t\tqueryAll( element, 'video, audio' ).forEach( el => {\n\t\t\t\tif( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Prefer an explicit global autoplay setting\n\t\t\t\tlet autoplay = this.Reveal.getConfig().autoPlayMedia;\n\n\t\t\t\t// If no global setting is available, fall back on the element's\n\t\t\t\t// own autoplay setting\n\t\t\t\tif( typeof autoplay !== 'boolean' ) {\n\t\t\t\t\tautoplay = el.hasAttribute( 'data-autoplay' ) || !!closest( el, '.slide-background' );\n\t\t\t\t}\n\n\t\t\t\tif( autoplay && typeof el.play === 'function' ) {\n\n\t\t\t\t\t// If the media is ready, start playback\n\t\t\t\t\tif( el.readyState > 1 ) {\n\t\t\t\t\t\tthis.startEmbeddedMedia( { target: el } );\n\t\t\t\t\t}\n\t\t\t\t\t// Mobile devices never fire a loaded event so instead\n\t\t\t\t\t// of waiting, we initiate playback\n\t\t\t\t\telse if( isMobile ) {\n\t\t\t\t\t\tlet promise = el.play();\n\n\t\t\t\t\t\t// If autoplay does not work, ensure that the controls are visible so\n\t\t\t\t\t\t// that the viewer can start the media on their own\n\t\t\t\t\t\tif( promise && typeof promise.catch === 'function' && el.controls === false ) {\n\t\t\t\t\t\t\tpromise.catch( () => {\n\t\t\t\t\t\t\t\tel.controls = true;\n\n\t\t\t\t\t\t\t\t// Once the video does start playing, hide the controls again\n\t\t\t\t\t\t\t\tel.addEventListener( 'play', () => {\n\t\t\t\t\t\t\t\t\tel.controls = false;\n\t\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// If the media isn't loaded, wait before playing\n\t\t\t\t\telse {\n\t\t\t\t\t\tel.removeEventListener( 'loadeddata', this.startEmbeddedMedia ); // remove first to avoid dupes\n\t\t\t\t\t\tel.addEventListener( 'loadeddata', this.startEmbeddedMedia );\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Normal iframes\n\t\t\tqueryAll( element, 'iframe[src]' ).forEach( el => {\n\t\t\t\tif( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.startEmbeddedIframe( { target: el } );\n\t\t\t} );\n\n\t\t\t// Lazy loading iframes\n\t\t\tqueryAll( element, 'iframe[data-src]' ).forEach( el => {\n\t\t\t\tif( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {\n\t\t\t\t\tel.removeEventListener( 'load', this.startEmbeddedIframe ); // remove first to avoid dupes\n\t\t\t\t\tel.addEventListener( 'load', this.startEmbeddedIframe );\n\t\t\t\t\tel.setAttribute( 'src', el.getAttribute( 'data-src' ) );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Starts playing an embedded video/audio element after\n\t * it has finished loading.\n\t *\n\t * @param {object} event\n\t */\n\tstartEmbeddedMedia( event ) {\n\n\t\tlet isAttachedToDOM = !!closest( event.target, 'html' ),\n\t\t\tisVisible \t\t= !!closest( event.target, '.present' );\n\n\t\tif( isAttachedToDOM && isVisible ) {\n\t\t\tevent.target.currentTime = 0;\n\t\t\tevent.target.play();\n\t\t}\n\n\t\tevent.target.removeEventListener( 'loadeddata', this.startEmbeddedMedia );\n\n\t}\n\n\t/**\n\t * \"Starts\" the content of an embedded iframe using the\n\t * postMessage API.\n\t *\n\t * @param {object} event\n\t */\n\tstartEmbeddedIframe( event ) {\n\n\t\tlet iframe = event.target;\n\n\t\tif( iframe && iframe.contentWindow ) {\n\n\t\t\tlet isAttachedToDOM = !!closest( event.target, 'html' ),\n\t\t\t\tisVisible \t\t= !!closest( event.target, '.present' );\n\n\t\t\tif( isAttachedToDOM && isVisible ) {\n\n\t\t\t\t// Prefer an explicit global autoplay setting\n\t\t\t\tlet autoplay = this.Reveal.getConfig().autoPlayMedia;\n\n\t\t\t\t// If no global setting is available, fall back on the element's\n\t\t\t\t// own autoplay setting\n\t\t\t\tif( typeof autoplay !== 'boolean' ) {\n\t\t\t\t\tautoplay = iframe.hasAttribute( 'data-autoplay' ) || !!closest( iframe, '.slide-background' );\n\t\t\t\t}\n\n\t\t\t\t// YouTube postMessage API\n\t\t\t\tif( /youtube\\.com\\/embed\\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {\n\t\t\t\t\tiframe.contentWindow.postMessage( '{\"event\":\"command\",\"func\":\"playVideo\",\"args\":\"\"}', '*' );\n\t\t\t\t}\n\t\t\t\t// Vimeo postMessage API\n\t\t\t\telse if( /player\\.vimeo\\.com\\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {\n\t\t\t\t\tiframe.contentWindow.postMessage( '{\"method\":\"play\"}', '*' );\n\t\t\t\t}\n\t\t\t\t// Generic postMessage API\n\t\t\t\telse {\n\t\t\t\t\tiframe.contentWindow.postMessage( 'slide:start', '*' );\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Stop playback of any embedded content inside of\n\t * the targeted slide.\n\t *\n\t * @param {HTMLElement} element\n\t */\n\tstopEmbeddedContent( element, options = {} ) {\n\n\t\toptions = extend( {\n\t\t\t// Defaults\n\t\t\tunloadIframes: true\n\t\t}, options );\n\n\t\tif( element && element.parentNode ) {\n\t\t\t// HTML5 media elements\n\t\t\tqueryAll( element, 'video, audio' ).forEach( el => {\n\t\t\t\tif( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {\n\t\t\t\t\tel.setAttribute('data-paused-by-reveal', '');\n\t\t\t\t\tel.pause();\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Generic postMessage API for non-lazy loaded iframes\n\t\t\tqueryAll( element, 'iframe' ).forEach( el => {\n\t\t\t\tif( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );\n\t\t\t\tel.removeEventListener( 'load', this.startEmbeddedIframe );\n\t\t\t});\n\n\t\t\t// YouTube postMessage API\n\t\t\tqueryAll( element, 'iframe[src*=\"youtube.com/embed/\"]' ).forEach( el => {\n\t\t\t\tif( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {\n\t\t\t\t\tel.contentWindow.postMessage( '{\"event\":\"command\",\"func\":\"pauseVideo\",\"args\":\"\"}', '*' );\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Vimeo postMessage API\n\t\t\tqueryAll( element, 'iframe[src*=\"player.vimeo.com/\"]' ).forEach( el => {\n\t\t\t\tif( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {\n\t\t\t\t\tel.contentWindow.postMessage( '{\"method\":\"pause\"}', '*' );\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif( options.unloadIframes === true ) {\n\t\t\t\t// Unload lazy-loaded iframes\n\t\t\t\tqueryAll( element, 'iframe[data-src]' ).forEach( el => {\n\t\t\t\t\t// Only removing the src doesn't actually unload the frame\n\t\t\t\t\t// in all browsers (Firefox) so we set it to blank first\n\t\t\t\t\tel.setAttribute( 'src', 'about:blank' );\n\t\t\t\t\tel.removeAttribute( 'src' );\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n","/**\n * Handles the display of reveal.js' optional slide number.\n */\nexport default class SlideNumber {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t}\n\n\trender() {\n\n\t\tthis.element = document.createElement( 'div' );\n\t\tthis.element.className = 'slide-number';\n\t\tthis.Reveal.getRevealElement().appendChild( this.element );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tlet slideNumberDisplay = 'none';\n\t\tif( config.slideNumber && !this.Reveal.isPrintingPDF() ) {\n\t\t\tif( config.showSlideNumber === 'all' ) {\n\t\t\t\tslideNumberDisplay = 'block';\n\t\t\t}\n\t\t\telse if( config.showSlideNumber === 'speaker' && this.Reveal.isSpeakerNotes() ) {\n\t\t\t\tslideNumberDisplay = 'block';\n\t\t\t}\n\t\t}\n\n\t\tthis.element.style.display = slideNumberDisplay;\n\n\t}\n\n\t/**\n\t * Updates the slide number to match the current slide.\n\t */\n\tupdate() {\n\n\t\t// Update slide number if enabled\n\t\tif( this.Reveal.getConfig().slideNumber && this.element ) {\n\t\t\tthis.element.innerHTML = this.getSlideNumber();\n\t\t}\n\n\t}\n\n\t/**\n\t * Returns the HTML string corresponding to the current slide\n\t * number, including formatting.\n\t */\n\tgetSlideNumber( slide = this.Reveal.getCurrentSlide() ) {\n\n\t\tlet config = this.Reveal.getConfig();\n\t\tlet value;\n\t\tlet format = 'h.v';\n\n\t\tif ( typeof config.slideNumber === 'function' ) {\n\t\t\tvalue = config.slideNumber( slide );\n\t\t} else {\n\t\t\t// Check if a custom number format is available\n\t\t\tif( typeof config.slideNumber === 'string' ) {\n\t\t\t\tformat = config.slideNumber;\n\t\t\t}\n\n\t\t\t// If there are ONLY vertical slides in this deck, always use\n\t\t\t// a flattened slide number\n\t\t\tif( !/c/.test( format ) && this.Reveal.getHorizontalSlides().length === 1 ) {\n\t\t\t\tformat = 'c';\n\t\t\t}\n\n\t\t\t// Offset the current slide number by 1 to make it 1-indexed\n\t\t\tlet horizontalOffset = slide && slide.dataset.visibility === 'uncounted' ? 0 : 1;\n\n\t\t\tvalue = [];\n\t\t\tswitch( format ) {\n\t\t\t\tcase 'c':\n\t\t\t\t\tvalue.push( this.Reveal.getSlidePastCount( slide ) + horizontalOffset );\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'c/t':\n\t\t\t\t\tvalue.push( this.Reveal.getSlidePastCount( slide ) + horizontalOffset, '/', this.Reveal.getTotalSlides() );\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tlet indices = this.Reveal.getIndices( slide );\n\t\t\t\t\tvalue.push( indices.h + horizontalOffset );\n\t\t\t\t\tlet sep = format === 'h/v' ? '/' : '.';\n\t\t\t\t\tif( this.Reveal.isVerticalSlide( slide ) ) value.push( sep, indices.v + 1 );\n\t\t\t}\n\t\t}\n\n\t\tlet url = '#' + this.Reveal.location.getHash( slide );\n\t\treturn this.formatNumber( value[0], value[1], value[2], url );\n\n\t}\n\n\t/**\n\t * Applies HTML formatting to a slide number before it's\n\t * written to the DOM.\n\t *\n\t * @param {number} a Current slide\n\t * @param {string} delimiter Character to separate slide numbers\n\t * @param {(number|*)} b Total slides\n\t * @param {HTMLElement} [url='#'+locationHash()] The url to link to\n\t * @return {string} HTML string fragment\n\t */\n\tformatNumber( a, delimiter, b, url = '#' + this.Reveal.location.getHash() ) {\n\n\t\tif( typeof b === 'number' && !isNaN( b ) ) {\n\t\t\treturn `\n\t\t\t\t\t${a}\n\t\t\t\t\t${delimiter}\n\t\t\t\t\t${b}\n\t\t\t\t\t`;\n\t\t}\n\t\telse {\n\t\t\treturn `\n\t\t\t\t\t${a}\n\t\t\t\t\t`;\n\t\t}\n\n\t}\n\n\tdestroy() {\n\n\t\tthis.element.remove();\n\n\t}\n\n}","/**\n * Converts various color input formats to an {r:0,g:0,b:0} object.\n *\n * @param {string} color The string representation of a color\n * @example\n * colorToRgb('#000');\n * @example\n * colorToRgb('#000000');\n * @example\n * colorToRgb('rgb(0,0,0)');\n * @example\n * colorToRgb('rgba(0,0,0)');\n *\n * @return {{r: number, g: number, b: number, [a]: number}|null}\n */\nexport const colorToRgb = ( color ) => {\n\n\tlet hex3 = color.match( /^#([0-9a-f]{3})$/i );\n\tif( hex3 && hex3[1] ) {\n\t\thex3 = hex3[1];\n\t\treturn {\n\t\t\tr: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,\n\t\t\tg: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,\n\t\t\tb: parseInt( hex3.charAt( 2 ), 16 ) * 0x11\n\t\t};\n\t}\n\n\tlet hex6 = color.match( /^#([0-9a-f]{6})$/i );\n\tif( hex6 && hex6[1] ) {\n\t\thex6 = hex6[1];\n\t\treturn {\n\t\t\tr: parseInt( hex6.slice( 0, 2 ), 16 ),\n\t\t\tg: parseInt( hex6.slice( 2, 4 ), 16 ),\n\t\t\tb: parseInt( hex6.slice( 4, 6 ), 16 )\n\t\t};\n\t}\n\n\tlet rgb = color.match( /^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/i );\n\tif( rgb ) {\n\t\treturn {\n\t\t\tr: parseInt( rgb[1], 10 ),\n\t\t\tg: parseInt( rgb[2], 10 ),\n\t\t\tb: parseInt( rgb[3], 10 )\n\t\t};\n\t}\n\n\tlet rgba = color.match( /^rgba\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\,\\s*([\\d]+|[\\d]*.[\\d]+)\\s*\\)$/i );\n\tif( rgba ) {\n\t\treturn {\n\t\t\tr: parseInt( rgba[1], 10 ),\n\t\t\tg: parseInt( rgba[2], 10 ),\n\t\t\tb: parseInt( rgba[3], 10 ),\n\t\t\ta: parseFloat( rgba[4] )\n\t\t};\n\t}\n\n\treturn null;\n\n}\n\n/**\n * Calculates brightness on a scale of 0-255.\n *\n * @param {string} color See colorToRgb for supported formats.\n * @see {@link colorToRgb}\n */\nexport const colorBrightness = ( color ) => {\n\n\tif( typeof color === 'string' ) color = colorToRgb( color );\n\n\tif( color ) {\n\t\treturn ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;\n\t}\n\n\treturn null;\n\n}","import { queryAll } from '../utils/util.js'\nimport { colorToRgb, colorBrightness } from '../utils/color.js'\n\n/**\n * Creates and updates slide backgrounds.\n */\nexport default class Backgrounds {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t}\n\n\trender() {\n\n\t\tthis.element = document.createElement( 'div' );\n\t\tthis.element.className = 'backgrounds';\n\t\tthis.Reveal.getRevealElement().appendChild( this.element );\n\n\t}\n\n\t/**\n\t * Creates the slide background elements and appends them\n\t * to the background container. One element is created per\n\t * slide no matter if the given slide has visible background.\n\t */\n\tcreate() {\n\n\t\t// Clear prior backgrounds\n\t\tthis.element.innerHTML = '';\n\t\tthis.element.classList.add( 'no-transition' );\n\n\t\t// Iterate over all horizontal slides\n\t\tthis.Reveal.getHorizontalSlides().forEach( slideh => {\n\n\t\t\tlet backgroundStack = this.createBackground( slideh, this.element );\n\n\t\t\t// Iterate over all vertical slides\n\t\t\tqueryAll( slideh, 'section' ).forEach( slidev => {\n\n\t\t\t\tthis.createBackground( slidev, backgroundStack );\n\n\t\t\t\tbackgroundStack.classList.add( 'stack' );\n\n\t\t\t} );\n\n\t\t} );\n\n\t\t// Add parallax background if specified\n\t\tif( this.Reveal.getConfig().parallaxBackgroundImage ) {\n\n\t\t\tthis.element.style.backgroundImage = 'url(\"' + this.Reveal.getConfig().parallaxBackgroundImage + '\")';\n\t\t\tthis.element.style.backgroundSize = this.Reveal.getConfig().parallaxBackgroundSize;\n\t\t\tthis.element.style.backgroundRepeat = this.Reveal.getConfig().parallaxBackgroundRepeat;\n\t\t\tthis.element.style.backgroundPosition = this.Reveal.getConfig().parallaxBackgroundPosition;\n\n\t\t\t// Make sure the below properties are set on the element - these properties are\n\t\t\t// needed for proper transitions to be set on the element via CSS. To remove\n\t\t\t// annoying background slide-in effect when the presentation starts, apply\n\t\t\t// these properties after short time delay\n\t\t\tsetTimeout( () => {\n\t\t\t\tthis.Reveal.getRevealElement().classList.add( 'has-parallax-background' );\n\t\t\t}, 1 );\n\n\t\t}\n\t\telse {\n\n\t\t\tthis.element.style.backgroundImage = '';\n\t\t\tthis.Reveal.getRevealElement().classList.remove( 'has-parallax-background' );\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Creates a background for the given slide.\n\t *\n\t * @param {HTMLElement} slide\n\t * @param {HTMLElement} container The element that the background\n\t * should be appended to\n\t * @return {HTMLElement} New background div\n\t */\n\tcreateBackground( slide, container ) {\n\n\t\t// Main slide background element\n\t\tlet element = document.createElement( 'div' );\n\t\telement.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );\n\n\t\t// Inner background element that wraps images/videos/iframes\n\t\tlet contentElement = document.createElement( 'div' );\n\t\tcontentElement.className = 'slide-background-content';\n\n\t\telement.appendChild( contentElement );\n\t\tcontainer.appendChild( element );\n\n\t\tslide.slideBackgroundElement = element;\n\t\tslide.slideBackgroundContentElement = contentElement;\n\n\t\t// Syncs the background to reflect all current background settings\n\t\tthis.sync( slide );\n\n\t\treturn element;\n\n\t}\n\n\t/**\n\t * Renders all of the visual properties of a slide background\n\t * based on the various background attributes.\n\t *\n\t * @param {HTMLElement} slide\n\t */\n\tsync( slide ) {\n\n\t\tconst element = slide.slideBackgroundElement,\n\t\t\tcontentElement = slide.slideBackgroundContentElement;\n\n\t\tconst data = {\n\t\t\tbackground: slide.getAttribute( 'data-background' ),\n\t\t\tbackgroundSize: slide.getAttribute( 'data-background-size' ),\n\t\t\tbackgroundImage: slide.getAttribute( 'data-background-image' ),\n\t\t\tbackgroundVideo: slide.getAttribute( 'data-background-video' ),\n\t\t\tbackgroundIframe: slide.getAttribute( 'data-background-iframe' ),\n\t\t\tbackgroundColor: slide.getAttribute( 'data-background-color' ),\n\t\t\tbackgroundRepeat: slide.getAttribute( 'data-background-repeat' ),\n\t\t\tbackgroundPosition: slide.getAttribute( 'data-background-position' ),\n\t\t\tbackgroundTransition: slide.getAttribute( 'data-background-transition' ),\n\t\t\tbackgroundOpacity: slide.getAttribute( 'data-background-opacity' ),\n\t\t};\n\n\t\tconst dataPreload = slide.hasAttribute( 'data-preload' );\n\n\t\t// Reset the prior background state in case this is not the\n\t\t// initial sync\n\t\tslide.classList.remove( 'has-dark-background' );\n\t\tslide.classList.remove( 'has-light-background' );\n\n\t\telement.removeAttribute( 'data-loaded' );\n\t\telement.removeAttribute( 'data-background-hash' );\n\t\telement.removeAttribute( 'data-background-size' );\n\t\telement.removeAttribute( 'data-background-transition' );\n\t\telement.style.backgroundColor = '';\n\n\t\tcontentElement.style.backgroundSize = '';\n\t\tcontentElement.style.backgroundRepeat = '';\n\t\tcontentElement.style.backgroundPosition = '';\n\t\tcontentElement.style.backgroundImage = '';\n\t\tcontentElement.style.opacity = '';\n\t\tcontentElement.innerHTML = '';\n\n\t\tif( data.background ) {\n\t\t\t// Auto-wrap image urls in url(...)\n\t\t\tif( /^(http|file|\\/\\/)/gi.test( data.background ) || /\\.(svg|png|jpg|jpeg|gif|bmp)([?#\\s]|$)/gi.test( data.background ) ) {\n\t\t\t\tslide.setAttribute( 'data-background-image', data.background );\n\t\t\t}\n\t\t\telse {\n\t\t\t\telement.style.background = data.background;\n\t\t\t}\n\t\t}\n\n\t\t// Create a hash for this combination of background settings.\n\t\t// This is used to determine when two slide backgrounds are\n\t\t// the same.\n\t\tif( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {\n\t\t\telement.setAttribute( 'data-background-hash', data.background +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundSize +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundImage +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundVideo +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundIframe +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundColor +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundRepeat +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundPosition +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundTransition +\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata.backgroundOpacity );\n\t\t}\n\n\t\t// Additional and optional background properties\n\t\tif( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );\n\t\tif( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;\n\t\tif( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );\n\n\t\tif( dataPreload ) element.setAttribute( 'data-preload', '' );\n\n\t\t// Background image options are set on the content wrapper\n\t\tif( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;\n\t\tif( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;\n\t\tif( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;\n\t\tif( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;\n\n\t\t// If this slide has a background color, we add a class that\n\t\t// signals if it is light or dark. If the slide has no background\n\t\t// color, no class will be added\n\t\tlet contrastColor = data.backgroundColor;\n\n\t\t// If no bg color was found, or it cannot be converted by colorToRgb, check the computed background\n\t\tif( !contrastColor || !colorToRgb( contrastColor ) ) {\n\t\t\tlet computedBackgroundStyle = window.getComputedStyle( element );\n\t\t\tif( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {\n\t\t\t\tcontrastColor = computedBackgroundStyle.backgroundColor;\n\t\t\t}\n\t\t}\n\n\t\tif( contrastColor ) {\n\t\t\tconst rgb = colorToRgb( contrastColor );\n\n\t\t\t// Ignore fully transparent backgrounds. Some browsers return\n\t\t\t// rgba(0,0,0,0) when reading the computed background color of\n\t\t\t// an element with no background\n\t\t\tif( rgb && rgb.a !== 0 ) {\n\t\t\t\tif( colorBrightness( contrastColor ) < 128 ) {\n\t\t\t\t\tslide.classList.add( 'has-dark-background' );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tslide.classList.add( 'has-light-background' );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\t/**\n\t * Updates the background elements to reflect the current\n\t * slide.\n\t *\n\t * @param {boolean} includeAll If true, the backgrounds of\n\t * all vertical slides (not just the present) will be updated.\n\t */\n\tupdate( includeAll = false ) {\n\n\t\tlet currentSlide = this.Reveal.getCurrentSlide();\n\t\tlet indices = this.Reveal.getIndices();\n\n\t\tlet currentBackground = null;\n\n\t\t// Reverse past/future classes when in RTL mode\n\t\tlet horizontalPast = this.Reveal.getConfig().rtl ? 'future' : 'past',\n\t\t\thorizontalFuture = this.Reveal.getConfig().rtl ? 'past' : 'future';\n\n\t\t// Update the classes of all backgrounds to match the\n\t\t// states of their slides (past/present/future)\n\t\tArray.from( this.element.childNodes ).forEach( ( backgroundh, h ) => {\n\n\t\t\tbackgroundh.classList.remove( 'past', 'present', 'future' );\n\n\t\t\tif( h < indices.h ) {\n\t\t\t\tbackgroundh.classList.add( horizontalPast );\n\t\t\t}\n\t\t\telse if ( h > indices.h ) {\n\t\t\t\tbackgroundh.classList.add( horizontalFuture );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbackgroundh.classList.add( 'present' );\n\n\t\t\t\t// Store a reference to the current background element\n\t\t\t\tcurrentBackground = backgroundh;\n\t\t\t}\n\n\t\t\tif( includeAll || h === indices.h ) {\n\t\t\t\tqueryAll( backgroundh, '.slide-background' ).forEach( ( backgroundv, v ) => {\n\n\t\t\t\t\tbackgroundv.classList.remove( 'past', 'present', 'future' );\n\n\t\t\t\t\tif( v < indices.v ) {\n\t\t\t\t\t\tbackgroundv.classList.add( 'past' );\n\t\t\t\t\t}\n\t\t\t\t\telse if ( v > indices.v ) {\n\t\t\t\t\t\tbackgroundv.classList.add( 'future' );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tbackgroundv.classList.add( 'present' );\n\n\t\t\t\t\t\t// Only if this is the present horizontal and vertical slide\n\t\t\t\t\t\tif( h === indices.h ) currentBackground = backgroundv;\n\t\t\t\t\t}\n\n\t\t\t\t} );\n\t\t\t}\n\n\t\t} );\n\n\t\t// Stop content inside of previous backgrounds\n\t\tif( this.previousBackground ) {\n\n\t\t\tthis.Reveal.slideContent.stopEmbeddedContent( this.previousBackground, { unloadIframes: !this.Reveal.slideContent.shouldPreload( this.previousBackground ) } );\n\n\t\t}\n\n\t\t// Start content in the current background\n\t\tif( currentBackground ) {\n\n\t\t\tthis.Reveal.slideContent.startEmbeddedContent( currentBackground );\n\n\t\t\tlet currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' );\n\t\t\tif( currentBackgroundContent ) {\n\n\t\t\t\tlet backgroundImageURL = currentBackgroundContent.style.backgroundImage || '';\n\n\t\t\t\t// Restart GIFs (doesn't work in Firefox)\n\t\t\t\tif( /\\.gif/i.test( backgroundImageURL ) ) {\n\t\t\t\t\tcurrentBackgroundContent.style.backgroundImage = '';\n\t\t\t\t\twindow.getComputedStyle( currentBackgroundContent ).opacity;\n\t\t\t\t\tcurrentBackgroundContent.style.backgroundImage = backgroundImageURL;\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Don't transition between identical backgrounds. This\n\t\t\t// prevents unwanted flicker.\n\t\t\tlet previousBackgroundHash = this.previousBackground ? this.previousBackground.getAttribute( 'data-background-hash' ) : null;\n\t\t\tlet currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );\n\t\t\tif( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) {\n\t\t\t\tthis.element.classList.add( 'no-transition' );\n\t\t\t}\n\n\t\t\tthis.previousBackground = currentBackground;\n\n\t\t}\n\n\t\t// If there's a background brightness flag for this slide,\n\t\t// bubble it to the .reveal container\n\t\tif( currentSlide ) {\n\t\t\t[ 'has-light-background', 'has-dark-background' ].forEach( classToBubble => {\n\t\t\t\tif( currentSlide.classList.contains( classToBubble ) ) {\n\t\t\t\t\tthis.Reveal.getRevealElement().classList.add( classToBubble );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.Reveal.getRevealElement().classList.remove( classToBubble );\n\t\t\t\t}\n\t\t\t}, this );\n\t\t}\n\n\t\t// Allow the first background to apply without transition\n\t\tsetTimeout( () => {\n\t\t\tthis.element.classList.remove( 'no-transition' );\n\t\t}, 1 );\n\n\t}\n\n\t/**\n\t * Updates the position of the parallax background based\n\t * on the current slide index.\n\t */\n\tupdateParallax() {\n\n\t\tlet indices = this.Reveal.getIndices();\n\n\t\tif( this.Reveal.getConfig().parallaxBackgroundImage ) {\n\n\t\t\tlet horizontalSlides = this.Reveal.getHorizontalSlides(),\n\t\t\t\tverticalSlides = this.Reveal.getVerticalSlides();\n\n\t\t\tlet backgroundSize = this.element.style.backgroundSize.split( ' ' ),\n\t\t\t\tbackgroundWidth, backgroundHeight;\n\n\t\t\tif( backgroundSize.length === 1 ) {\n\t\t\t\tbackgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbackgroundWidth = parseInt( backgroundSize[0], 10 );\n\t\t\t\tbackgroundHeight = parseInt( backgroundSize[1], 10 );\n\t\t\t}\n\n\t\t\tlet slideWidth = this.element.offsetWidth,\n\t\t\t\thorizontalSlideCount = horizontalSlides.length,\n\t\t\t\thorizontalOffsetMultiplier,\n\t\t\t\thorizontalOffset;\n\n\t\t\tif( typeof this.Reveal.getConfig().parallaxBackgroundHorizontal === 'number' ) {\n\t\t\t\thorizontalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundHorizontal;\n\t\t\t}\n\t\t\telse {\n\t\t\t\thorizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0;\n\t\t\t}\n\n\t\t\thorizontalOffset = horizontalOffsetMultiplier * indices.h * -1;\n\n\t\t\tlet slideHeight = this.element.offsetHeight,\n\t\t\t\tverticalSlideCount = verticalSlides.length,\n\t\t\t\tverticalOffsetMultiplier,\n\t\t\t\tverticalOffset;\n\n\t\t\tif( typeof this.Reveal.getConfig().parallaxBackgroundVertical === 'number' ) {\n\t\t\t\tverticalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundVertical;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tverticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );\n\t\t\t}\n\n\t\t\tverticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indices.v : 0;\n\n\t\t\tthis.element.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';\n\n\t\t}\n\n\t}\n\n\tdestroy() {\n\n\t\tthis.element.remove();\n\n\t}\n\n}\n","\nexport const SLIDES_SELECTOR = '.slides section';\nexport const HORIZONTAL_SLIDES_SELECTOR = '.slides>section';\nexport const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section';\n\n// Methods that may not be invoked via the postMessage API\nexport const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener/;\n\n// Regex for retrieving the fragment style from a class attribute\nexport const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/;","import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util.js'\nimport { FRAGMENT_STYLE_REGEX } from '../utils/constants.js'\n\n// Counter used to generate unique IDs for auto-animated elements\nlet autoAnimateCounter = 0;\n\n/**\n * Automatically animates matching elements across\n * slides with the [data-auto-animate] attribute.\n */\nexport default class AutoAnimate {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t}\n\n\t/**\n\t * Runs an auto-animation between the given slides.\n\t *\n\t * @param {HTMLElement} fromSlide\n\t * @param {HTMLElement} toSlide\n\t */\n\trun( fromSlide, toSlide ) {\n\n\t\t// Clean up after prior animations\n\t\tthis.reset();\n\n\t\tlet allSlides = this.Reveal.getSlides();\n\t\tlet toSlideIndex = allSlides.indexOf( toSlide );\n\t\tlet fromSlideIndex = allSlides.indexOf( fromSlide );\n\n\t\t// Ensure that both slides are auto-animate targets with the same data-auto-animate-id value\n\t\t// (including null if absent on both) and that data-auto-animate-restart isn't set on the\n\t\t// physically latter slide (independent of slide direction)\n\t\tif( fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' )\n\t\t\t\t&& fromSlide.getAttribute( 'data-auto-animate-id' ) === toSlide.getAttribute( 'data-auto-animate-id' ) \n\t\t\t\t&& !( toSlideIndex > fromSlideIndex ? toSlide : fromSlide ).hasAttribute( 'data-auto-animate-restart' ) ) {\n\n\t\t\t// Create a new auto-animate sheet\n\t\t\tthis.autoAnimateStyleSheet = this.autoAnimateStyleSheet || createStyleSheet();\n\n\t\t\tlet animationOptions = this.getAutoAnimateOptions( toSlide );\n\n\t\t\t// Set our starting state\n\t\t\tfromSlide.dataset.autoAnimate = 'pending';\n\t\t\ttoSlide.dataset.autoAnimate = 'pending';\n\n\t\t\t// Flag the navigation direction, needed for fragment buildup\n\t\t\tanimationOptions.slideDirection = toSlideIndex > fromSlideIndex ? 'forward' : 'backward';\n\n\t\t\t// Inject our auto-animate styles for this transition\n\t\t\tlet css = this.getAutoAnimatableElements( fromSlide, toSlide ).map( elements => {\n\t\t\t\treturn this.autoAnimateElements( elements.from, elements.to, elements.options || {}, animationOptions, autoAnimateCounter++ );\n\t\t\t} );\n\n\t\t\t// Animate unmatched elements, if enabled\n\t\t\tif( toSlide.dataset.autoAnimateUnmatched !== 'false' && this.Reveal.getConfig().autoAnimateUnmatched === true ) {\n\n\t\t\t\t// Our default timings for unmatched elements\n\t\t\t\tlet defaultUnmatchedDuration = animationOptions.duration * 0.8,\n\t\t\t\t\tdefaultUnmatchedDelay = animationOptions.duration * 0.2;\n\n\t\t\t\tthis.getUnmatchedAutoAnimateElements( toSlide ).forEach( unmatchedElement => {\n\n\t\t\t\t\tlet unmatchedOptions = this.getAutoAnimateOptions( unmatchedElement, animationOptions );\n\t\t\t\t\tlet id = 'unmatched';\n\n\t\t\t\t\t// If there is a duration or delay set specifically for this\n\t\t\t\t\t// element our unmatched elements should adhere to those\n\t\t\t\t\tif( unmatchedOptions.duration !== animationOptions.duration || unmatchedOptions.delay !== animationOptions.delay ) {\n\t\t\t\t\t\tid = 'unmatched-' + autoAnimateCounter++;\n\t\t\t\t\t\tcss.push( `[data-auto-animate=\"running\"] [data-auto-animate-target=\"${id}\"] { transition: opacity ${unmatchedOptions.duration}s ease ${unmatchedOptions.delay}s; }` );\n\t\t\t\t\t}\n\n\t\t\t\t\tunmatchedElement.dataset.autoAnimateTarget = id;\n\n\t\t\t\t}, this );\n\n\t\t\t\t// Our default transition for unmatched elements\n\t\t\t\tcss.push( `[data-auto-animate=\"running\"] [data-auto-animate-target=\"unmatched\"] { transition: opacity ${defaultUnmatchedDuration}s ease ${defaultUnmatchedDelay}s; }` );\n\n\t\t\t}\n\n\t\t\t// Setting the whole chunk of CSS at once is the most\n\t\t\t// efficient way to do this. Using sheet.insertRule\n\t\t\t// is multiple factors slower.\n\t\t\tthis.autoAnimateStyleSheet.innerHTML = css.join( '' );\n\n\t\t\t// Start the animation next cycle\n\t\t\trequestAnimationFrame( () => {\n\t\t\t\tif( this.autoAnimateStyleSheet ) {\n\t\t\t\t\t// This forces our newly injected styles to be applied in Firefox\n\t\t\t\t\tgetComputedStyle( this.autoAnimateStyleSheet ).fontWeight;\n\n\t\t\t\t\ttoSlide.dataset.autoAnimate = 'running';\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\ttype: 'autoanimate',\n\t\t\t\tdata: {\n\t\t\t\t\tfromSlide,\n\t\t\t\t\ttoSlide,\n\t\t\t\t\tsheet: this.autoAnimateStyleSheet\n\t\t\t\t}\n\t\t\t});\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Rolls back all changes that we've made to the DOM so\n\t * that as part of animating.\n\t */\n\treset() {\n\n\t\t// Reset slides\n\t\tqueryAll( this.Reveal.getRevealElement(), '[data-auto-animate]:not([data-auto-animate=\"\"])' ).forEach( element => {\n\t\t\telement.dataset.autoAnimate = '';\n\t\t} );\n\n\t\t// Reset elements\n\t\tqueryAll( this.Reveal.getRevealElement(), '[data-auto-animate-target]' ).forEach( element => {\n\t\t\tdelete element.dataset.autoAnimateTarget;\n\t\t} );\n\n\t\t// Remove the animation sheet\n\t\tif( this.autoAnimateStyleSheet && this.autoAnimateStyleSheet.parentNode ) {\n\t\t\tthis.autoAnimateStyleSheet.parentNode.removeChild( this.autoAnimateStyleSheet );\n\t\t\tthis.autoAnimateStyleSheet = null;\n\t\t}\n\n\t}\n\n\t/**\n\t * Creates a FLIP animation where the `to` element starts out\n\t * in the `from` element position and animates to its original\n\t * state.\n\t *\n\t * @param {HTMLElement} from\n\t * @param {HTMLElement} to\n\t * @param {Object} elementOptions Options for this element pair\n\t * @param {Object} animationOptions Options set at the slide level\n\t * @param {String} id Unique ID that we can use to identify this\n\t * auto-animate element in the DOM\n\t */\n\tautoAnimateElements( from, to, elementOptions, animationOptions, id ) {\n\n\t\t// 'from' elements are given a data-auto-animate-target with no value,\n\t\t// 'to' elements are are given a data-auto-animate-target with an ID\n\t\tfrom.dataset.autoAnimateTarget = '';\n\t\tto.dataset.autoAnimateTarget = id;\n\n\t\t// Each element may override any of the auto-animate options\n\t\t// like transition easing, duration and delay via data-attributes\n\t\tlet options = this.getAutoAnimateOptions( to, animationOptions );\n\n\t\t// If we're using a custom element matcher the element options\n\t\t// may contain additional transition overrides\n\t\tif( typeof elementOptions.delay !== 'undefined' ) options.delay = elementOptions.delay;\n\t\tif( typeof elementOptions.duration !== 'undefined' ) options.duration = elementOptions.duration;\n\t\tif( typeof elementOptions.easing !== 'undefined' ) options.easing = elementOptions.easing;\n\n\t\tlet fromProps = this.getAutoAnimatableProperties( 'from', from, elementOptions ),\n\t\t\ttoProps = this.getAutoAnimatableProperties( 'to', to, elementOptions );\n\n\t\t// Maintain fragment visibility for matching elements when\n\t\t// we're navigating forwards, this way the viewer won't need\n\t\t// to step through the same fragments twice\n\t\tif( to.classList.contains( 'fragment' ) ) {\n\n\t\t\t// Don't auto-animate the opacity of fragments to avoid\n\t\t\t// conflicts with fragment animations\n\t\t\tdelete toProps.styles['opacity'];\n\n\t\t\tif( from.classList.contains( 'fragment' ) ) {\n\n\t\t\t\tlet fromFragmentStyle = ( from.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0];\n\t\t\t\tlet toFragmentStyle = ( to.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0];\n\n\t\t\t\t// Only skip the fragment if the fragment animation style\n\t\t\t\t// remains unchanged\n\t\t\t\tif( fromFragmentStyle === toFragmentStyle && animationOptions.slideDirection === 'forward' ) {\n\t\t\t\t\tto.classList.add( 'visible', 'disabled' );\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\t// If translation and/or scaling are enabled, css transform\n\t\t// the 'to' element so that it matches the position and size\n\t\t// of the 'from' element\n\t\tif( elementOptions.translate !== false || elementOptions.scale !== false ) {\n\n\t\t\tlet presentationScale = this.Reveal.getScale();\n\n\t\t\tlet delta = {\n\t\t\t\tx: ( fromProps.x - toProps.x ) / presentationScale,\n\t\t\t\ty: ( fromProps.y - toProps.y ) / presentationScale,\n\t\t\t\tscaleX: fromProps.width / toProps.width,\n\t\t\t\tscaleY: fromProps.height / toProps.height\n\t\t\t};\n\n\t\t\t// Limit decimal points to avoid 0.0001px blur and stutter\n\t\t\tdelta.x = Math.round( delta.x * 1000 ) / 1000;\n\t\t\tdelta.y = Math.round( delta.y * 1000 ) / 1000;\n\t\t\tdelta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000;\n\t\t\tdelta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000;\n\n\t\t\tlet translate = elementOptions.translate !== false && ( delta.x !== 0 || delta.y !== 0 ),\n\t\t\t\tscale = elementOptions.scale !== false && ( delta.scaleX !== 0 || delta.scaleY !== 0 );\n\n\t\t\t// No need to transform if nothing's changed\n\t\t\tif( translate || scale ) {\n\n\t\t\t\tlet transform = [];\n\n\t\t\t\tif( translate ) transform.push( `translate(${delta.x}px, ${delta.y}px)` );\n\t\t\t\tif( scale ) transform.push( `scale(${delta.scaleX}, ${delta.scaleY})` );\n\n\t\t\t\tfromProps.styles['transform'] = transform.join( ' ' );\n\t\t\t\tfromProps.styles['transform-origin'] = 'top left';\n\n\t\t\t\ttoProps.styles['transform'] = 'none';\n\n\t\t\t}\n\n\t\t}\n\n\t\t// Delete all unchanged 'to' styles\n\t\tfor( let propertyName in toProps.styles ) {\n\t\t\tconst toValue = toProps.styles[propertyName];\n\t\t\tconst fromValue = fromProps.styles[propertyName];\n\n\t\t\tif( toValue === fromValue ) {\n\t\t\t\tdelete toProps.styles[propertyName];\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// If these property values were set via a custom matcher providing\n\t\t\t\t// an explicit 'from' and/or 'to' value, we always inject those values.\n\t\t\t\tif( toValue.explicitValue === true ) {\n\t\t\t\t\ttoProps.styles[propertyName] = toValue.value;\n\t\t\t\t}\n\n\t\t\t\tif( fromValue.explicitValue === true ) {\n\t\t\t\t\tfromProps.styles[propertyName] = fromValue.value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet css = '';\n\n\t\tlet toStyleProperties = Object.keys( toProps.styles );\n\n\t\t// Only create animate this element IF at least one style\n\t\t// property has changed\n\t\tif( toStyleProperties.length > 0 ) {\n\n\t\t\t// Instantly move to the 'from' state\n\t\t\tfromProps.styles['transition'] = 'none';\n\n\t\t\t// Animate towards the 'to' state\n\t\t\ttoProps.styles['transition'] = `all ${options.duration}s ${options.easing} ${options.delay}s`;\n\t\t\ttoProps.styles['transition-property'] = toStyleProperties.join( ', ' );\n\t\t\ttoProps.styles['will-change'] = toStyleProperties.join( ', ' );\n\n\t\t\t// Build up our custom CSS. We need to override inline styles\n\t\t\t// so we need to make our styles vErY IMPORTANT!1!!\n\t\t\tlet fromCSS = Object.keys( fromProps.styles ).map( propertyName => {\n\t\t\t\treturn propertyName + ': ' + fromProps.styles[propertyName] + ' !important;';\n\t\t\t} ).join( '' );\n\n\t\t\tlet toCSS = Object.keys( toProps.styles ).map( propertyName => {\n\t\t\t\treturn propertyName + ': ' + toProps.styles[propertyName] + ' !important;';\n\t\t\t} ).join( '' );\n\n\t\t\tcss = \t'[data-auto-animate-target=\"'+ id +'\"] {'+ fromCSS +'}' +\n\t\t\t\t\t'[data-auto-animate=\"running\"] [data-auto-animate-target=\"'+ id +'\"] {'+ toCSS +'}';\n\n\t\t}\n\n\t\treturn css;\n\n\t}\n\n\t/**\n\t * Returns the auto-animate options for the given element.\n\t *\n\t * @param {HTMLElement} element Element to pick up options\n\t * from, either a slide or an animation target\n\t * @param {Object} [inheritedOptions] Optional set of existing\n\t * options\n\t */\n\tgetAutoAnimateOptions( element, inheritedOptions ) {\n\n\t\tlet options = {\n\t\t\teasing: this.Reveal.getConfig().autoAnimateEasing,\n\t\t\tduration: this.Reveal.getConfig().autoAnimateDuration,\n\t\t\tdelay: 0\n\t\t};\n\n\t\toptions = extend( options, inheritedOptions );\n\n\t\t// Inherit options from parent elements\n\t\tif( element.parentNode ) {\n\t\t\tlet autoAnimatedParent = closest( element.parentNode, '[data-auto-animate-target]' );\n\t\t\tif( autoAnimatedParent ) {\n\t\t\t\toptions = this.getAutoAnimateOptions( autoAnimatedParent, options );\n\t\t\t}\n\t\t}\n\n\t\tif( element.dataset.autoAnimateEasing ) {\n\t\t\toptions.easing = element.dataset.autoAnimateEasing;\n\t\t}\n\n\t\tif( element.dataset.autoAnimateDuration ) {\n\t\t\toptions.duration = parseFloat( element.dataset.autoAnimateDuration );\n\t\t}\n\n\t\tif( element.dataset.autoAnimateDelay ) {\n\t\t\toptions.delay = parseFloat( element.dataset.autoAnimateDelay );\n\t\t}\n\n\t\treturn options;\n\n\t}\n\n\t/**\n\t * Returns an object containing all of the properties\n\t * that can be auto-animated for the given element and\n\t * their current computed values.\n\t *\n\t * @param {String} direction 'from' or 'to'\n\t */\n\tgetAutoAnimatableProperties( direction, element, elementOptions ) {\n\n\t\tlet config = this.Reveal.getConfig();\n\n\t\tlet properties = { styles: [] };\n\n\t\t// Position and size\n\t\tif( elementOptions.translate !== false || elementOptions.scale !== false ) {\n\t\t\tlet bounds;\n\n\t\t\t// Custom auto-animate may optionally return a custom tailored\n\t\t\t// measurement function\n\t\t\tif( typeof elementOptions.measure === 'function' ) {\n\t\t\t\tbounds = elementOptions.measure( element );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif( config.center ) {\n\t\t\t\t\t// More precise, but breaks when used in combination\n\t\t\t\t\t// with zoom for scaling the deck ¯\\_(ツ)_/¯\n\t\t\t\t\tbounds = element.getBoundingClientRect();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlet scale = this.Reveal.getScale();\n\t\t\t\t\tbounds = {\n\t\t\t\t\t\tx: element.offsetLeft * scale,\n\t\t\t\t\t\ty: element.offsetTop * scale,\n\t\t\t\t\t\twidth: element.offsetWidth * scale,\n\t\t\t\t\t\theight: element.offsetHeight * scale\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tproperties.x = bounds.x;\n\t\t\tproperties.y = bounds.y;\n\t\t\tproperties.width = bounds.width;\n\t\t\tproperties.height = bounds.height;\n\t\t}\n\n\t\tconst computedStyles = getComputedStyle( element );\n\n\t\t// CSS styles\n\t\t( elementOptions.styles || config.autoAnimateStyles ).forEach( style => {\n\t\t\tlet value;\n\n\t\t\t// `style` is either the property name directly, or an object\n\t\t\t// definition of a style property\n\t\t\tif( typeof style === 'string' ) style = { property: style };\n\n\t\t\tif( typeof style.from !== 'undefined' && direction === 'from' ) {\n\t\t\t\tvalue = { value: style.from, explicitValue: true };\n\t\t\t}\n\t\t\telse if( typeof style.to !== 'undefined' && direction === 'to' ) {\n\t\t\t\tvalue = { value: style.to, explicitValue: true };\n\t\t\t}\n\t\t\telse {\n\t\t\t\tvalue = computedStyles[style.property];\n\t\t\t}\n\n\t\t\tif( value !== '' ) {\n\t\t\t\tproperties.styles[style.property] = value;\n\t\t\t}\n\t\t} );\n\n\t\treturn properties;\n\n\t}\n\n\t/**\n\t * Get a list of all element pairs that we can animate\n\t * between the given slides.\n\t *\n\t * @param {HTMLElement} fromSlide\n\t * @param {HTMLElement} toSlide\n\t *\n\t * @return {Array} Each value is an array where [0] is\n\t * the element we're animating from and [1] is the\n\t * element we're animating to\n\t */\n\tgetAutoAnimatableElements( fromSlide, toSlide ) {\n\n\t\tlet matcher = typeof this.Reveal.getConfig().autoAnimateMatcher === 'function' ? this.Reveal.getConfig().autoAnimateMatcher : this.getAutoAnimatePairs;\n\n\t\tlet pairs = matcher.call( this, fromSlide, toSlide );\n\n\t\tlet reserved = [];\n\n\t\t// Remove duplicate pairs\n\t\treturn pairs.filter( ( pair, index ) => {\n\t\t\tif( reserved.indexOf( pair.to ) === -1 ) {\n\t\t\t\treserved.push( pair.to );\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} );\n\n\t}\n\n\t/**\n\t * Identifies matching elements between slides.\n\t *\n\t * You can specify a custom matcher function by using\n\t * the `autoAnimateMatcher` config option.\n\t */\n\tgetAutoAnimatePairs( fromSlide, toSlide ) {\n\n\t\tlet pairs = [];\n\n\t\tconst codeNodes = 'pre';\n\t\tconst textNodes = 'h1, h2, h3, h4, h5, h6, p, li';\n\t\tconst mediaNodes = 'img, video, iframe';\n\n\t\t// Eplicit matches via data-id\n\t\tthis.findAutoAnimateMatches( pairs, fromSlide, toSlide, '[data-id]', node => {\n\t\t\treturn node.nodeName + ':::' + node.getAttribute( 'data-id' );\n\t\t} );\n\n\t\t// Text\n\t\tthis.findAutoAnimateMatches( pairs, fromSlide, toSlide, textNodes, node => {\n\t\t\treturn node.nodeName + ':::' + node.innerText;\n\t\t} );\n\n\t\t// Media\n\t\tthis.findAutoAnimateMatches( pairs, fromSlide, toSlide, mediaNodes, node => {\n\t\t\treturn node.nodeName + ':::' + ( node.getAttribute( 'src' ) || node.getAttribute( 'data-src' ) );\n\t\t} );\n\n\t\t// Code\n\t\tthis.findAutoAnimateMatches( pairs, fromSlide, toSlide, codeNodes, node => {\n\t\t\treturn node.nodeName + ':::' + node.innerText;\n\t\t} );\n\n\t\tpairs.forEach( pair => {\n\n\t\t\t// Disable scale transformations on text nodes, we transition\n\t\t\t// each individual text property instead\n\t\t\tif( matches( pair.from, textNodes ) ) {\n\t\t\t\tpair.options = { scale: false };\n\t\t\t}\n\t\t\t// Animate individual lines of code\n\t\t\telse if( matches( pair.from, codeNodes ) ) {\n\n\t\t\t\t// Transition the code block's width and height instead of scaling\n\t\t\t\t// to prevent its content from being squished\n\t\t\t\tpair.options = { scale: false, styles: [ 'width', 'height' ] };\n\n\t\t\t\t// Lines of code\n\t\t\t\tthis.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-code', node => {\n\t\t\t\t\treturn node.textContent;\n\t\t\t\t}, {\n\t\t\t\t\tscale: false,\n\t\t\t\t\tstyles: [],\n\t\t\t\t\tmeasure: this.getLocalBoundingBox.bind( this )\n\t\t\t\t} );\n\n\t\t\t\t// Line numbers\n\t\t\t\tthis.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-line[data-line-number]', node => {\n\t\t\t\t\treturn node.getAttribute( 'data-line-number' );\n\t\t\t\t}, {\n\t\t\t\t\tscale: false,\n\t\t\t\t\tstyles: [ 'width' ],\n\t\t\t\t\tmeasure: this.getLocalBoundingBox.bind( this )\n\t\t\t\t} );\n\n\t\t\t}\n\n\t\t}, this );\n\n\t\treturn pairs;\n\n\t}\n\n\t/**\n\t * Helper method which returns a bounding box based on\n\t * the given elements offset coordinates.\n\t *\n\t * @param {HTMLElement} element\n\t * @return {Object} x, y, width, height\n\t */\n\tgetLocalBoundingBox( element ) {\n\n\t\tconst presentationScale = this.Reveal.getScale();\n\n\t\treturn {\n\t\t\tx: Math.round( ( element.offsetLeft * presentationScale ) * 100 ) / 100,\n\t\t\ty: Math.round( ( element.offsetTop * presentationScale ) * 100 ) / 100,\n\t\t\twidth: Math.round( ( element.offsetWidth * presentationScale ) * 100 ) / 100,\n\t\t\theight: Math.round( ( element.offsetHeight * presentationScale ) * 100 ) / 100\n\t\t};\n\n\t}\n\n\t/**\n\t * Finds matching elements between two slides.\n\t *\n\t * @param {Array} pairs \tList of pairs to push matches to\n\t * @param {HTMLElement} fromScope Scope within the from element exists\n\t * @param {HTMLElement} toScope Scope within the to element exists\n\t * @param {String} selector CSS selector of the element to match\n\t * @param {Function} serializer A function that accepts an element and returns\n\t * a stringified ID based on its contents\n\t * @param {Object} animationOptions Optional config options for this pair\n\t */\n\tfindAutoAnimateMatches( pairs, fromScope, toScope, selector, serializer, animationOptions ) {\n\n\t\tlet fromMatches = {};\n\t\tlet toMatches = {};\n\n\t\t[].slice.call( fromScope.querySelectorAll( selector ) ).forEach( ( element, i ) => {\n\t\t\tconst key = serializer( element );\n\t\t\tif( typeof key === 'string' && key.length ) {\n\t\t\t\tfromMatches[key] = fromMatches[key] || [];\n\t\t\t\tfromMatches[key].push( element );\n\t\t\t}\n\t\t} );\n\n\t\t[].slice.call( toScope.querySelectorAll( selector ) ).forEach( ( element, i ) => {\n\t\t\tconst key = serializer( element );\n\t\t\ttoMatches[key] = toMatches[key] || [];\n\t\t\ttoMatches[key].push( element );\n\n\t\t\tlet fromElement;\n\n\t\t\t// Retrieve the 'from' element\n\t\t\tif( fromMatches[key] ) {\n\t\t\t\tconst pimaryIndex = toMatches[key].length - 1;\n\t\t\t\tconst secondaryIndex = fromMatches[key].length - 1;\n\n\t\t\t\t// If there are multiple identical from elements, retrieve\n\t\t\t\t// the one at the same index as our to-element.\n\t\t\t\tif( fromMatches[key][ pimaryIndex ] ) {\n\t\t\t\t\tfromElement = fromMatches[key][ pimaryIndex ];\n\t\t\t\t\tfromMatches[key][ pimaryIndex ] = null;\n\t\t\t\t}\n\t\t\t\t// If there are no matching from-elements at the same index,\n\t\t\t\t// use the last one.\n\t\t\t\telse if( fromMatches[key][ secondaryIndex ] ) {\n\t\t\t\t\tfromElement = fromMatches[key][ secondaryIndex ];\n\t\t\t\t\tfromMatches[key][ secondaryIndex ] = null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we've got a matching pair, push it to the list of pairs\n\t\t\tif( fromElement ) {\n\t\t\t\tpairs.push({\n\t\t\t\t\tfrom: fromElement,\n\t\t\t\t\tto: element,\n\t\t\t\t\toptions: animationOptions\n\t\t\t\t});\n\t\t\t}\n\t\t} );\n\n\t}\n\n\t/**\n\t * Returns a all elements within the given scope that should\n\t * be considered unmatched in an auto-animate transition. If\n\t * fading of unmatched elements is turned on, these elements\n\t * will fade when going between auto-animate slides.\n\t *\n\t * Note that parents of auto-animate targets are NOT considerd\n\t * unmatched since fading them would break the auto-animation.\n\t *\n\t * @param {HTMLElement} rootElement\n\t * @return {Array}\n\t */\n\tgetUnmatchedAutoAnimateElements( rootElement ) {\n\n\t\treturn [].slice.call( rootElement.children ).reduce( ( result, element ) => {\n\n\t\t\tconst containsAnimatedElements = element.querySelector( '[data-auto-animate-target]' );\n\n\t\t\t// The element is unmatched if\n\t\t\t// - It is not an auto-animate target\n\t\t\t// - It does not contain any auto-animate targets\n\t\t\tif( !element.hasAttribute( 'data-auto-animate-target' ) && !containsAnimatedElements ) {\n\t\t\t\tresult.push( element );\n\t\t\t}\n\n\t\t\tif( element.querySelector( '[data-auto-animate-target]' ) ) {\n\t\t\t\tresult = result.concat( this.getUnmatchedAutoAnimateElements( element ) );\n\t\t\t}\n\n\t\t\treturn result;\n\n\t\t}, [] );\n\n\t}\n\n}\n","import { extend, queryAll } from '../utils/util.js'\n\n/**\n * Handles sorting and navigation of slide fragments.\n * Fragments are elements within a slide that are\n * revealed/animated incrementally.\n */\nexport default class Fragments {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tif( config.fragments === false ) {\n\t\t\tthis.disable();\n\t\t}\n\t\telse if( oldConfig.fragments === false ) {\n\t\t\tthis.enable();\n\t\t}\n\n\t}\n\n\t/**\n\t * If fragments are disabled in the deck, they should all be\n\t * visible rather than stepped through.\n\t */\n\tdisable() {\n\n\t\tqueryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => {\n\t\t\telement.classList.add( 'visible' );\n\t\t\telement.classList.remove( 'current-fragment' );\n\t\t} );\n\n\t}\n\n\t/**\n\t * Reverse of #disable(). Only called if fragments have\n\t * previously been disabled.\n\t */\n\tenable() {\n\n\t\tqueryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => {\n\t\t\telement.classList.remove( 'visible' );\n\t\t\telement.classList.remove( 'current-fragment' );\n\t\t} );\n\n\t}\n\n\t/**\n\t * Returns an object describing the available fragment\n\t * directions.\n\t *\n\t * @return {{prev: boolean, next: boolean}}\n\t */\n\tavailableRoutes() {\n\n\t\tlet currentSlide = this.Reveal.getCurrentSlide();\n\t\tif( currentSlide && this.Reveal.getConfig().fragments ) {\n\t\t\tlet fragments = currentSlide.querySelectorAll( '.fragment:not(.disabled)' );\n\t\t\tlet hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.disabled):not(.visible)' );\n\n\t\t\treturn {\n\t\t\t\tprev: fragments.length - hiddenFragments.length > 0,\n\t\t\t\tnext: !!hiddenFragments.length\n\t\t\t};\n\t\t}\n\t\telse {\n\t\t\treturn { prev: false, next: false };\n\t\t}\n\n\t}\n\n\t/**\n\t * Return a sorted fragments list, ordered by an increasing\n\t * \"data-fragment-index\" attribute.\n\t *\n\t * Fragments will be revealed in the order that they are returned by\n\t * this function, so you can use the index attributes to control the\n\t * order of fragment appearance.\n\t *\n\t * To maintain a sensible default fragment order, fragments are presumed\n\t * to be passed in document order. This function adds a \"fragment-index\"\n\t * attribute to each node if such an attribute is not already present,\n\t * and sets that attribute to an integer value which is the position of\n\t * the fragment within the fragments list.\n\t *\n\t * @param {object[]|*} fragments\n\t * @param {boolean} grouped If true the returned array will contain\n\t * nested arrays for all fragments with the same index\n\t * @return {object[]} sorted Sorted array of fragments\n\t */\n\tsort( fragments, grouped = false ) {\n\n\t\tfragments = Array.from( fragments );\n\n\t\tlet ordered = [],\n\t\t\tunordered = [],\n\t\t\tsorted = [];\n\n\t\t// Group ordered and unordered elements\n\t\tfragments.forEach( fragment => {\n\t\t\tif( fragment.hasAttribute( 'data-fragment-index' ) ) {\n\t\t\t\tlet index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );\n\n\t\t\t\tif( !ordered[index] ) {\n\t\t\t\t\tordered[index] = [];\n\t\t\t\t}\n\n\t\t\t\tordered[index].push( fragment );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tunordered.push( [ fragment ] );\n\t\t\t}\n\t\t} );\n\n\t\t// Append fragments without explicit indices in their\n\t\t// DOM order\n\t\tordered = ordered.concat( unordered );\n\n\t\t// Manually count the index up per group to ensure there\n\t\t// are no gaps\n\t\tlet index = 0;\n\n\t\t// Push all fragments in their sorted order to an array,\n\t\t// this flattens the groups\n\t\tordered.forEach( group => {\n\t\t\tgroup.forEach( fragment => {\n\t\t\t\tsorted.push( fragment );\n\t\t\t\tfragment.setAttribute( 'data-fragment-index', index );\n\t\t\t} );\n\n\t\t\tindex ++;\n\t\t} );\n\n\t\treturn grouped === true ? ordered : sorted;\n\n\t}\n\n\t/**\n\t * Sorts and formats all of fragments in the\n\t * presentation.\n\t */\n\tsortAll() {\n\n\t\tthis.Reveal.getHorizontalSlides().forEach( horizontalSlide => {\n\n\t\t\tlet verticalSlides = queryAll( horizontalSlide, 'section' );\n\t\t\tverticalSlides.forEach( ( verticalSlide, y ) => {\n\n\t\t\t\tthis.sort( verticalSlide.querySelectorAll( '.fragment' ) );\n\n\t\t\t}, this );\n\n\t\t\tif( verticalSlides.length === 0 ) this.sort( horizontalSlide.querySelectorAll( '.fragment' ) );\n\n\t\t} );\n\n\t}\n\n\t/**\n\t * Refreshes the fragments on the current slide so that they\n\t * have the appropriate classes (.visible + .current-fragment).\n\t *\n\t * @param {number} [index] The index of the current fragment\n\t * @param {array} [fragments] Array containing all fragments\n\t * in the current slide\n\t *\n\t * @return {{shown: array, hidden: array}}\n\t */\n\tupdate( index, fragments ) {\n\n\t\tlet changedFragments = {\n\t\t\tshown: [],\n\t\t\thidden: []\n\t\t};\n\n\t\tlet currentSlide = this.Reveal.getCurrentSlide();\n\t\tif( currentSlide && this.Reveal.getConfig().fragments ) {\n\n\t\t\tfragments = fragments || this.sort( currentSlide.querySelectorAll( '.fragment' ) );\n\n\t\t\tif( fragments.length ) {\n\n\t\t\t\tlet maxIndex = 0;\n\n\t\t\t\tif( typeof index !== 'number' ) {\n\t\t\t\t\tlet currentFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();\n\t\t\t\t\tif( currentFragment ) {\n\t\t\t\t\t\tindex = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tArray.from( fragments ).forEach( ( el, i ) => {\n\n\t\t\t\t\tif( el.hasAttribute( 'data-fragment-index' ) ) {\n\t\t\t\t\t\ti = parseInt( el.getAttribute( 'data-fragment-index' ), 10 );\n\t\t\t\t\t}\n\n\t\t\t\t\tmaxIndex = Math.max( maxIndex, i );\n\n\t\t\t\t\t// Visible fragments\n\t\t\t\t\tif( i <= index ) {\n\t\t\t\t\t\tlet wasVisible = el.classList.contains( 'visible' )\n\t\t\t\t\t\tel.classList.add( 'visible' );\n\t\t\t\t\t\tel.classList.remove( 'current-fragment' );\n\n\t\t\t\t\t\tif( i === index ) {\n\t\t\t\t\t\t\t// Announce the fragments one by one to the Screen Reader\n\t\t\t\t\t\t\tthis.Reveal.announceStatus( this.Reveal.getStatusText( el ) );\n\n\t\t\t\t\t\t\tel.classList.add( 'current-fragment' );\n\t\t\t\t\t\t\tthis.Reveal.slideContent.startEmbeddedContent( el );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif( !wasVisible ) {\n\t\t\t\t\t\t\tchangedFragments.shown.push( el )\n\t\t\t\t\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\t\t\t\t\ttarget: el,\n\t\t\t\t\t\t\t\ttype: 'visible',\n\t\t\t\t\t\t\t\tbubbles: false\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Hidden fragments\n\t\t\t\t\telse {\n\t\t\t\t\t\tlet wasVisible = el.classList.contains( 'visible' )\n\t\t\t\t\t\tel.classList.remove( 'visible' );\n\t\t\t\t\t\tel.classList.remove( 'current-fragment' );\n\n\t\t\t\t\t\tif( wasVisible ) {\n\t\t\t\t\t\t\tthis.Reveal.slideContent.stopEmbeddedContent( el );\n\t\t\t\t\t\t\tchangedFragments.hidden.push( el );\n\t\t\t\t\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\t\t\t\t\ttarget: el,\n\t\t\t\t\t\t\t\ttype: 'hidden',\n\t\t\t\t\t\t\t\tbubbles: false\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t} );\n\n\t\t\t\t// Write the current fragment index to the slide
    .\n\t\t\t\t// This can be used by end users to apply styles based on\n\t\t\t\t// the current fragment index.\n\t\t\t\tindex = typeof index === 'number' ? index : -1;\n\t\t\t\tindex = Math.max( Math.min( index, maxIndex ), -1 );\n\t\t\t\tcurrentSlide.setAttribute( 'data-fragment', index );\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn changedFragments;\n\n\t}\n\n\t/**\n\t * Formats the fragments on the given slide so that they have\n\t * valid indices. Call this if fragments are changed in the DOM\n\t * after reveal.js has already initialized.\n\t *\n\t * @param {HTMLElement} slide\n\t * @return {Array} a list of the HTML fragments that were synced\n\t */\n\tsync( slide = this.Reveal.getCurrentSlide() ) {\n\n\t\treturn this.sort( slide.querySelectorAll( '.fragment' ) );\n\n\t}\n\n\t/**\n\t * Navigate to the specified slide fragment.\n\t *\n\t * @param {?number} index The index of the fragment that\n\t * should be shown, -1 means all are invisible\n\t * @param {number} offset Integer offset to apply to the\n\t * fragment index\n\t *\n\t * @return {boolean} true if a change was made in any\n\t * fragments visibility as part of this call\n\t */\n\tgoto( index, offset = 0 ) {\n\n\t\tlet currentSlide = this.Reveal.getCurrentSlide();\n\t\tif( currentSlide && this.Reveal.getConfig().fragments ) {\n\n\t\t\tlet fragments = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled)' ) );\n\t\t\tif( fragments.length ) {\n\n\t\t\t\t// If no index is specified, find the current\n\t\t\t\tif( typeof index !== 'number' ) {\n\t\t\t\t\tlet lastVisibleFragment = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled).visible' ) ).pop();\n\n\t\t\t\t\tif( lastVisibleFragment ) {\n\t\t\t\t\t\tindex = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tindex = -1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply the offset if there is one\n\t\t\t\tindex += offset;\n\n\t\t\t\tlet changedFragments = this.update( index, fragments );\n\n\t\t\t\tif( changedFragments.hidden.length ) {\n\t\t\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\t\t\ttype: 'fragmenthidden',\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tfragment: changedFragments.hidden[0],\n\t\t\t\t\t\t\tfragments: changedFragments.hidden\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif( changedFragments.shown.length ) {\n\t\t\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\t\t\ttype: 'fragmentshown',\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tfragment: changedFragments.shown[0],\n\t\t\t\t\t\t\tfragments: changedFragments.shown\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.Reveal.controls.update();\n\t\t\t\tthis.Reveal.progress.update();\n\n\t\t\t\tif( this.Reveal.getConfig().fragmentInURL ) {\n\t\t\t\t\tthis.Reveal.location.writeURL();\n\t\t\t\t}\n\n\t\t\t\treturn !!( changedFragments.shown.length || changedFragments.hidden.length );\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn false;\n\n\t}\n\n\t/**\n\t * Navigate to the next slide fragment.\n\t *\n\t * @return {boolean} true if there was a next fragment,\n\t * false otherwise\n\t */\n\tnext() {\n\n\t\treturn this.goto( null, 1 );\n\n\t}\n\n\t/**\n\t * Navigate to the previous slide fragment.\n\t *\n\t * @return {boolean} true if there was a previous fragment,\n\t * false otherwise\n\t */\n\tprev() {\n\n\t\treturn this.goto( null, -1 );\n\n\t}\n\n}","import { SLIDES_SELECTOR } from '../utils/constants.js'\nimport { extend, queryAll, transformElement } from '../utils/util.js'\n\n/**\n * Handles all logic related to the overview mode\n * (birds-eye view of all slides).\n */\nexport default class Overview {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\tthis.active = false;\n\n\t\tthis.onSlideClicked = this.onSlideClicked.bind( this );\n\n\t}\n\n\t/**\n\t * Displays the overview of slides (quick nav) by scaling\n\t * down and arranging all slide elements.\n\t */\n\tactivate() {\n\n\t\t// Only proceed if enabled in config\n\t\tif( this.Reveal.getConfig().overview && !this.isActive() ) {\n\n\t\t\tthis.active = true;\n\n\t\t\tthis.Reveal.getRevealElement().classList.add( 'overview' );\n\n\t\t\t// Don't auto-slide while in overview mode\n\t\t\tthis.Reveal.cancelAutoSlide();\n\n\t\t\t// Move the backgrounds element into the slide container to\n\t\t\t// that the same scaling is applied\n\t\t\tthis.Reveal.getSlidesElement().appendChild( this.Reveal.getBackgroundsElement() );\n\n\t\t\t// Clicking on an overview slide navigates to it\n\t\t\tqueryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => {\n\t\t\t\tif( !slide.classList.contains( 'stack' ) ) {\n\t\t\t\t\tslide.addEventListener( 'click', this.onSlideClicked, true );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Calculate slide sizes\n\t\t\tconst margin = 70;\n\t\t\tconst slideSize = this.Reveal.getComputedSlideSize();\n\t\t\tthis.overviewSlideWidth = slideSize.width + margin;\n\t\t\tthis.overviewSlideHeight = slideSize.height + margin;\n\n\t\t\t// Reverse in RTL mode\n\t\t\tif( this.Reveal.getConfig().rtl ) {\n\t\t\t\tthis.overviewSlideWidth = -this.overviewSlideWidth;\n\t\t\t}\n\n\t\t\tthis.Reveal.updateSlidesVisibility();\n\n\t\t\tthis.layout();\n\t\t\tthis.update();\n\n\t\t\tthis.Reveal.layout();\n\n\t\t\tconst indices = this.Reveal.getIndices();\n\n\t\t\t// Notify observers of the overview showing\n\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\ttype: 'overviewshown',\n\t\t\t\tdata: {\n\t\t\t\t\t'indexh': indices.h,\n\t\t\t\t\t'indexv': indices.v,\n\t\t\t\t\t'currentSlide': this.Reveal.getCurrentSlide()\n\t\t\t\t}\n\t\t\t});\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Uses CSS transforms to position all slides in a grid for\n\t * display inside of the overview mode.\n\t */\n\tlayout() {\n\n\t\t// Layout slides\n\t\tthis.Reveal.getHorizontalSlides().forEach( ( hslide, h ) => {\n\t\t\thslide.setAttribute( 'data-index-h', h );\n\t\t\ttransformElement( hslide, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' );\n\n\t\t\tif( hslide.classList.contains( 'stack' ) ) {\n\n\t\t\t\tqueryAll( hslide, 'section' ).forEach( ( vslide, v ) => {\n\t\t\t\t\tvslide.setAttribute( 'data-index-h', h );\n\t\t\t\t\tvslide.setAttribute( 'data-index-v', v );\n\n\t\t\t\t\ttransformElement( vslide, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' );\n\t\t\t\t} );\n\n\t\t\t}\n\t\t} );\n\n\t\t// Layout slide backgrounds\n\t\tArray.from( this.Reveal.getBackgroundsElement().childNodes ).forEach( ( hbackground, h ) => {\n\t\t\ttransformElement( hbackground, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' );\n\n\t\t\tqueryAll( hbackground, '.slide-background' ).forEach( ( vbackground, v ) => {\n\t\t\t\ttransformElement( vbackground, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' );\n\t\t\t} );\n\t\t} );\n\n\t}\n\n\t/**\n\t * Moves the overview viewport to the current slides.\n\t * Called each time the current slide changes.\n\t */\n\tupdate() {\n\n\t\tconst vmin = Math.min( window.innerWidth, window.innerHeight );\n\t\tconst scale = Math.max( vmin / 5, 150 ) / vmin;\n\t\tconst indices = this.Reveal.getIndices();\n\n\t\tthis.Reveal.transformSlides( {\n\t\t\toverview: [\n\t\t\t\t'scale('+ scale +')',\n\t\t\t\t'translateX('+ ( -indices.h * this.overviewSlideWidth ) +'px)',\n\t\t\t\t'translateY('+ ( -indices.v * this.overviewSlideHeight ) +'px)'\n\t\t\t].join( ' ' )\n\t\t} );\n\n\t}\n\n\t/**\n\t * Exits the slide overview and enters the currently\n\t * active slide.\n\t */\n\tdeactivate() {\n\n\t\t// Only proceed if enabled in config\n\t\tif( this.Reveal.getConfig().overview ) {\n\n\t\t\tthis.active = false;\n\n\t\t\tthis.Reveal.getRevealElement().classList.remove( 'overview' );\n\n\t\t\t// Temporarily add a class so that transitions can do different things\n\t\t\t// depending on whether they are exiting/entering overview, or just\n\t\t\t// moving from slide to slide\n\t\t\tthis.Reveal.getRevealElement().classList.add( 'overview-deactivating' );\n\n\t\t\tsetTimeout( () => {\n\t\t\t\tthis.Reveal.getRevealElement().classList.remove( 'overview-deactivating' );\n\t\t\t}, 1 );\n\n\t\t\t// Move the background element back out\n\t\t\tthis.Reveal.getRevealElement().appendChild( this.Reveal.getBackgroundsElement() );\n\n\t\t\t// Clean up changes made to slides\n\t\t\tqueryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => {\n\t\t\t\ttransformElement( slide, '' );\n\n\t\t\t\tslide.removeEventListener( 'click', this.onSlideClicked, true );\n\t\t\t} );\n\n\t\t\t// Clean up changes made to backgrounds\n\t\t\tqueryAll( this.Reveal.getBackgroundsElement(), '.slide-background' ).forEach( background => {\n\t\t\t\ttransformElement( background, '' );\n\t\t\t} );\n\n\t\t\tthis.Reveal.transformSlides( { overview: '' } );\n\n\t\t\tconst indices = this.Reveal.getIndices();\n\n\t\t\tthis.Reveal.slide( indices.h, indices.v );\n\t\t\tthis.Reveal.layout();\n\t\t\tthis.Reveal.cueAutoSlide();\n\n\t\t\t// Notify observers of the overview hiding\n\t\t\tthis.Reveal.dispatchEvent({\n\t\t\t\ttype: 'overviewhidden',\n\t\t\t\tdata: {\n\t\t\t\t\t'indexh': indices.h,\n\t\t\t\t\t'indexv': indices.v,\n\t\t\t\t\t'currentSlide': this.Reveal.getCurrentSlide()\n\t\t\t\t}\n\t\t\t});\n\n\t\t}\n\t}\n\n\t/**\n\t * Toggles the slide overview mode on and off.\n\t *\n\t * @param {Boolean} [override] Flag which overrides the\n\t * toggle logic and forcibly sets the desired state. True means\n\t * overview is open, false means it's closed.\n\t */\n\ttoggle( override ) {\n\n\t\tif( typeof override === 'boolean' ) {\n\t\t\toverride ? this.activate() : this.deactivate();\n\t\t}\n\t\telse {\n\t\t\tthis.isActive() ? this.deactivate() : this.activate();\n\t\t}\n\n\t}\n\n\t/**\n\t * Checks if the overview is currently active.\n\t *\n\t * @return {Boolean} true if the overview is active,\n\t * false otherwise\n\t */\n\tisActive() {\n\n\t\treturn this.active;\n\n\t}\n\n\t/**\n\t * Invoked when a slide is and we're in the overview.\n\t *\n\t * @param {object} event\n\t */\n\tonSlideClicked( event ) {\n\n\t\tif( this.isActive() ) {\n\t\t\tevent.preventDefault();\n\n\t\t\tlet element = event.target;\n\n\t\t\twhile( element && !element.nodeName.match( /section/gi ) ) {\n\t\t\t\telement = element.parentNode;\n\t\t\t}\n\n\t\t\tif( element && !element.classList.contains( 'disabled' ) ) {\n\n\t\t\t\tthis.deactivate();\n\n\t\t\t\tif( element.nodeName.match( /section/gi ) ) {\n\t\t\t\t\tlet h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),\n\t\t\t\t\t\tv = parseInt( element.getAttribute( 'data-index-v' ), 10 );\n\n\t\t\t\t\tthis.Reveal.slide( h, v );\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t}\n\n}","import { enterFullscreen } from '../utils/util.js'\n\n/**\n * Handles all reveal.js keyboard interactions.\n */\nexport default class Keyboard {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\t// A key:value map of keyboard keys and descriptions of\n\t\t// the actions they trigger\n\t\tthis.shortcuts = {};\n\n\t\t// Holds custom key code mappings\n\t\tthis.bindings = {};\n\n\t\tthis.onDocumentKeyDown = this.onDocumentKeyDown.bind( this );\n\t\tthis.onDocumentKeyPress = this.onDocumentKeyPress.bind( this );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tif( config.navigationMode === 'linear' ) {\n\t\t\tthis.shortcuts['→ , ↓ , SPACE , N , L , J'] = 'Next slide';\n\t\t\tthis.shortcuts['← , ↑ , P , H , K'] = 'Previous slide';\n\t\t}\n\t\telse {\n\t\t\tthis.shortcuts['N , SPACE'] = 'Next slide';\n\t\t\tthis.shortcuts['P , Shift SPACE'] = 'Previous slide';\n\t\t\tthis.shortcuts['← , H'] = 'Navigate left';\n\t\t\tthis.shortcuts['→ , L'] = 'Navigate right';\n\t\t\tthis.shortcuts['↑ , K'] = 'Navigate up';\n\t\t\tthis.shortcuts['↓ , J'] = 'Navigate down';\n\t\t}\n\n\t\tthis.shortcuts['Alt + ←/↑/→/↓'] = 'Navigate without fragments';\n\t\tthis.shortcuts['Shift + ←/↑/→/↓'] = 'Jump to first/last slide';\n\t\tthis.shortcuts['B , .'] = 'Pause';\n\t\tthis.shortcuts['F'] = 'Fullscreen';\n\t\tthis.shortcuts['ESC, O'] = 'Slide overview';\n\n\t}\n\n\t/**\n\t * Starts listening for keyboard events.\n\t */\n\tbind() {\n\n\t\tdocument.addEventListener( 'keydown', this.onDocumentKeyDown, false );\n\t\tdocument.addEventListener( 'keypress', this.onDocumentKeyPress, false );\n\n\t}\n\n\t/**\n\t * Stops listening for keyboard events.\n\t */\n\tunbind() {\n\n\t\tdocument.removeEventListener( 'keydown', this.onDocumentKeyDown, false );\n\t\tdocument.removeEventListener( 'keypress', this.onDocumentKeyPress, false );\n\n\t}\n\n\t/**\n\t * Add a custom key binding with optional description to\n\t * be added to the help screen.\n\t */\n\taddKeyBinding( binding, callback ) {\n\n\t\tif( typeof binding === 'object' && binding.keyCode ) {\n\t\t\tthis.bindings[binding.keyCode] = {\n\t\t\t\tcallback: callback,\n\t\t\t\tkey: binding.key,\n\t\t\t\tdescription: binding.description\n\t\t\t};\n\t\t}\n\t\telse {\n\t\t\tthis.bindings[binding] = {\n\t\t\t\tcallback: callback,\n\t\t\t\tkey: null,\n\t\t\t\tdescription: null\n\t\t\t};\n\t\t}\n\n\t}\n\n\t/**\n\t * Removes the specified custom key binding.\n\t */\n\tremoveKeyBinding( keyCode ) {\n\n\t\tdelete this.bindings[keyCode];\n\n\t}\n\n\t/**\n\t * Programmatically triggers a keyboard event\n\t *\n\t * @param {int} keyCode\n\t */\n\ttriggerKey( keyCode ) {\n\n\t\tthis.onDocumentKeyDown( { keyCode } );\n\n\t}\n\n\t/**\n\t * Registers a new shortcut to include in the help overlay\n\t *\n\t * @param {String} key\n\t * @param {String} value\n\t */\n\tregisterKeyboardShortcut( key, value ) {\n\n\t\tthis.shortcuts[key] = value;\n\n\t}\n\n\tgetShortcuts() {\n\n\t\treturn this.shortcuts;\n\n\t}\n\n\tgetBindings() {\n\n\t\treturn this.bindings;\n\n\t}\n\n\t/**\n\t * Handler for the document level 'keypress' event.\n\t *\n\t * @param {object} event\n\t */\n\tonDocumentKeyPress( event ) {\n\n\t\t// Check if the pressed key is question mark\n\t\tif( event.shiftKey && event.charCode === 63 ) {\n\t\t\tthis.Reveal.toggleHelp();\n\t\t}\n\n\t}\n\n\t/**\n\t * Handler for the document level 'keydown' event.\n\t *\n\t * @param {object} event\n\t */\n\tonDocumentKeyDown( event ) {\n\n\t\tlet config = this.Reveal.getConfig();\n\n\t\t// If there's a condition specified and it returns false,\n\t\t// ignore this event\n\t\tif( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// If keyboardCondition is set, only capture keyboard events\n\t\t// for embedded decks when they are focused\n\t\tif( config.keyboardCondition === 'focused' && !this.Reveal.isFocused() ) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Shorthand\n\t\tlet keyCode = event.keyCode;\n\n\t\t// Remember if auto-sliding was paused so we can toggle it\n\t\tlet autoSlideWasPaused = !this.Reveal.isAutoSliding();\n\n\t\tthis.Reveal.onUserInput( event );\n\n\t\t// Is there a focused element that could be using the keyboard?\n\t\tlet activeElementIsCE = document.activeElement && document.activeElement.isContentEditable === true;\n\t\tlet activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );\n\t\tlet activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);\n\n\t\t// Whitelist certain modifiers for slide navigation shortcuts\n\t\tlet isNavigationKey = [32, 37, 38, 39, 40, 78, 80].indexOf( event.keyCode ) !== -1;\n\n\t\t// Prevent all other events when a modifier is pressed\n\t\tlet unusedModifier = \t!( isNavigationKey && event.shiftKey || event.altKey ) &&\n\t\t\t\t\t\t\t\t( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey );\n\n\t\t// Disregard the event if there's a focused element or a\n\t\t// keyboard modifier key is present\n\t\tif( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return;\n\n\t\t// While paused only allow resume keyboard events; 'b', 'v', '.'\n\t\tlet resumeKeyCodes = [66,86,190,191];\n\t\tlet key;\n\n\t\t// Custom key bindings for togglePause should be able to resume\n\t\tif( typeof config.keyboard === 'object' ) {\n\t\t\tfor( key in config.keyboard ) {\n\t\t\t\tif( config.keyboard[key] === 'togglePause' ) {\n\t\t\t\t\tresumeKeyCodes.push( parseInt( key, 10 ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif( this.Reveal.isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Use linear navigation if we're configured to OR if\n\t\t// the presentation is one-dimensional\n\t\tlet useLinearMode = config.navigationMode === 'linear' || !this.Reveal.hasHorizontalSlides() || !this.Reveal.hasVerticalSlides();\n\n\t\tlet triggered = false;\n\n\t\t// 1. User defined key bindings\n\t\tif( typeof config.keyboard === 'object' ) {\n\n\t\t\tfor( key in config.keyboard ) {\n\n\t\t\t\t// Check if this binding matches the pressed key\n\t\t\t\tif( parseInt( key, 10 ) === keyCode ) {\n\n\t\t\t\t\tlet value = config.keyboard[ key ];\n\n\t\t\t\t\t// Callback function\n\t\t\t\t\tif( typeof value === 'function' ) {\n\t\t\t\t\t\tvalue.apply( null, [ event ] );\n\t\t\t\t\t}\n\t\t\t\t\t// String shortcuts to reveal.js API\n\t\t\t\t\telse if( typeof value === 'string' && typeof this.Reveal[ value ] === 'function' ) {\n\t\t\t\t\t\tthis.Reveal[ value ].call();\n\t\t\t\t\t}\n\n\t\t\t\t\ttriggered = true;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\t// 2. Registered custom key bindings\n\t\tif( triggered === false ) {\n\n\t\t\tfor( key in this.bindings ) {\n\n\t\t\t\t// Check if this binding matches the pressed key\n\t\t\t\tif( parseInt( key, 10 ) === keyCode ) {\n\n\t\t\t\t\tlet action = this.bindings[ key ].callback;\n\n\t\t\t\t\t// Callback function\n\t\t\t\t\tif( typeof action === 'function' ) {\n\t\t\t\t\t\taction.apply( null, [ event ] );\n\t\t\t\t\t}\n\t\t\t\t\t// String shortcuts to reveal.js API\n\t\t\t\t\telse if( typeof action === 'string' && typeof this.Reveal[ action ] === 'function' ) {\n\t\t\t\t\t\tthis.Reveal[ action ].call();\n\t\t\t\t\t}\n\n\t\t\t\t\ttriggered = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 3. System defined key bindings\n\t\tif( triggered === false ) {\n\n\t\t\t// Assume true and try to prove false\n\t\t\ttriggered = true;\n\n\t\t\t// P, PAGE UP\n\t\t\tif( keyCode === 80 || keyCode === 33 ) {\n\t\t\t\tthis.Reveal.prev({skipFragments: event.altKey});\n\t\t\t}\n\t\t\t// N, PAGE DOWN\n\t\t\telse if( keyCode === 78 || keyCode === 34 ) {\n\t\t\t\tthis.Reveal.next({skipFragments: event.altKey});\n\t\t\t}\n\t\t\t// H, LEFT\n\t\t\telse if( keyCode === 72 || keyCode === 37 ) {\n\t\t\t\tif( event.shiftKey ) {\n\t\t\t\t\tthis.Reveal.slide( 0 );\n\t\t\t\t}\n\t\t\t\telse if( !this.Reveal.overview.isActive() && useLinearMode ) {\n\t\t\t\t\tthis.Reveal.prev({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.Reveal.left({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// L, RIGHT\n\t\t\telse if( keyCode === 76 || keyCode === 39 ) {\n\t\t\t\tif( event.shiftKey ) {\n\t\t\t\t\tthis.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );\n\t\t\t\t}\n\t\t\t\telse if( !this.Reveal.overview.isActive() && useLinearMode ) {\n\t\t\t\t\tthis.Reveal.next({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.Reveal.right({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// K, UP\n\t\t\telse if( keyCode === 75 || keyCode === 38 ) {\n\t\t\t\tif( event.shiftKey ) {\n\t\t\t\t\tthis.Reveal.slide( undefined, 0 );\n\t\t\t\t}\n\t\t\t\telse if( !this.Reveal.overview.isActive() && useLinearMode ) {\n\t\t\t\t\tthis.Reveal.prev({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.Reveal.up({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// J, DOWN\n\t\t\telse if( keyCode === 74 || keyCode === 40 ) {\n\t\t\t\tif( event.shiftKey ) {\n\t\t\t\t\tthis.Reveal.slide( undefined, Number.MAX_VALUE );\n\t\t\t\t}\n\t\t\t\telse if( !this.Reveal.overview.isActive() && useLinearMode ) {\n\t\t\t\t\tthis.Reveal.next({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.Reveal.down({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// HOME\n\t\t\telse if( keyCode === 36 ) {\n\t\t\t\tthis.Reveal.slide( 0 );\n\t\t\t}\n\t\t\t// END\n\t\t\telse if( keyCode === 35 ) {\n\t\t\t\tthis.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );\n\t\t\t}\n\t\t\t// SPACE\n\t\t\telse if( keyCode === 32 ) {\n\t\t\t\tif( this.Reveal.overview.isActive() ) {\n\t\t\t\t\tthis.Reveal.overview.deactivate();\n\t\t\t\t}\n\t\t\t\tif( event.shiftKey ) {\n\t\t\t\t\tthis.Reveal.prev({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.Reveal.next({skipFragments: event.altKey});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS \"BLACK SCREEN\" BUTTON\n\t\t\telse if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) {\n\t\t\t\tthis.Reveal.togglePause();\n\t\t\t}\n\t\t\t// F\n\t\t\telse if( keyCode === 70 ) {\n\t\t\t\tenterFullscreen( config.embedded ? this.Reveal.getViewportElement() : document.documentElement );\n\t\t\t}\n\t\t\t// A\n\t\t\telse if( keyCode === 65 ) {\n\t\t\t\tif ( config.autoSlideStoppable ) {\n\t\t\t\t\tthis.Reveal.toggleAutoSlide( autoSlideWasPaused );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttriggered = false;\n\t\t\t}\n\n\t\t}\n\n\t\t// If the input resulted in a triggered action we should prevent\n\t\t// the browsers default behavior\n\t\tif( triggered ) {\n\t\t\tevent.preventDefault && event.preventDefault();\n\t\t}\n\t\t// ESC or O key\n\t\telse if( keyCode === 27 || keyCode === 79 ) {\n\t\t\tif( this.Reveal.closeOverlay() === false ) {\n\t\t\t\tthis.Reveal.overview.toggle();\n\t\t\t}\n\n\t\t\tevent.preventDefault && event.preventDefault();\n\t\t}\n\n\t\t// If auto-sliding is enabled we need to cue up\n\t\t// another timeout\n\t\tthis.Reveal.cueAutoSlide();\n\n\t}\n\n}","/**\n * Reads and writes the URL based on reveal.js' current state.\n */\nexport default class Location {\n\n\t// The minimum number of milliseconds that must pass between\n\t// calls to history.replaceState\n\tMAX_REPLACE_STATE_FREQUENCY = 1000\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\t// Delays updates to the URL due to a Chrome thumbnailer bug\n\t\tthis.writeURLTimeout = 0;\n\n\t\tthis.replaceStateTimestamp = 0;\n\n\t\tthis.onWindowHashChange = this.onWindowHashChange.bind( this );\n\n\t}\n\n\tbind() {\n\n\t\twindow.addEventListener( 'hashchange', this.onWindowHashChange, false );\n\n\t}\n\n\tunbind() {\n\n\t\twindow.removeEventListener( 'hashchange', this.onWindowHashChange, false );\n\n\t}\n\n\t/**\n\t * Returns the slide indices for the given hash link.\n\t *\n\t * @param {string} [hash] the hash string that we want to\n\t * find the indices for\n\t *\n\t * @returns slide indices or null\n\t */\n\tgetIndicesFromHash( hash=window.location.hash ) {\n\n\t\t// Attempt to parse the hash as either an index or name\n\t\tlet name = hash.replace( /^#\\/?/, '' );\n\t\tlet bits = name.split( '/' );\n\n\t\t// If the first bit is not fully numeric and there is a name we\n\t\t// can assume that this is a named link\n\t\tif( !/^[0-9]*$/.test( bits[0] ) && name.length ) {\n\t\t\tlet element;\n\n\t\t\tlet f;\n\n\t\t\t// Parse named links with fragments (#/named-link/2)\n\t\t\tif( /\\/[-\\d]+$/g.test( name ) ) {\n\t\t\t\tf = parseInt( name.split( '/' ).pop(), 10 );\n\t\t\t\tf = isNaN(f) ? undefined : f;\n\t\t\t\tname = name.split( '/' ).shift();\n\t\t\t}\n\n\t\t\t// Ensure the named link is a valid HTML ID attribute\n\t\t\ttry {\n\t\t\t\telement = document.getElementById( decodeURIComponent( name ) );\n\t\t\t}\n\t\t\tcatch ( error ) { }\n\n\t\t\tif( element ) {\n\t\t\t\treturn { ...this.Reveal.getIndices( element ), f };\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tconst config = this.Reveal.getConfig();\n\t\t\tlet hashIndexBase = config.hashOneBasedIndex ? 1 : 0;\n\n\t\t\t// Read the index components of the hash\n\t\t\tlet h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0,\n\t\t\t\tv = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0,\n\t\t\t\tf;\n\n\t\t\tif( config.fragmentInURL ) {\n\t\t\t\tf = parseInt( bits[2], 10 );\n\t\t\t\tif( isNaN( f ) ) {\n\t\t\t\t\tf = undefined;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { h, v, f };\n\t\t}\n\n\t\t// The hash couldn't be parsed or no matching named link was found\n\t\treturn null\n\n\t}\n\n\t/**\n\t * Reads the current URL (hash) and navigates accordingly.\n\t */\n\treadURL() {\n\n\t\tconst currentIndices = this.Reveal.getIndices();\n\t\tconst newIndices = this.getIndicesFromHash();\n\n\t\tif( newIndices ) {\n\t\t\tif( ( newIndices.h !== currentIndices.h || newIndices.v !== currentIndices.v || newIndices.f !== undefined ) ) {\n\t\t\t\t\tthis.Reveal.slide( newIndices.h, newIndices.v, newIndices.f );\n\t\t\t}\n\t\t}\n\t\t// If no new indices are available, we're trying to navigate to\n\t\t// a slide hash that does not exist\n\t\telse {\n\t\t\tthis.Reveal.slide( currentIndices.h || 0, currentIndices.v || 0 );\n\t\t}\n\n\t}\n\n\t/**\n\t * Updates the page URL (hash) to reflect the current\n\t * state.\n\t *\n\t * @param {number} delay The time in ms to wait before\n\t * writing the hash\n\t */\n\twriteURL( delay ) {\n\n\t\tlet config = this.Reveal.getConfig();\n\t\tlet currentSlide = this.Reveal.getCurrentSlide();\n\n\t\t// Make sure there's never more than one timeout running\n\t\tclearTimeout( this.writeURLTimeout );\n\n\t\t// If a delay is specified, timeout this call\n\t\tif( typeof delay === 'number' ) {\n\t\t\tthis.writeURLTimeout = setTimeout( this.writeURL, delay );\n\t\t}\n\t\telse if( currentSlide ) {\n\n\t\t\tlet hash = this.getHash();\n\n\t\t\t// If we're configured to push to history OR the history\n\t\t\t// API is not avaialble.\n\t\t\tif( config.history ) {\n\t\t\t\twindow.location.hash = hash;\n\t\t\t}\n\t\t\t// If we're configured to reflect the current slide in the\n\t\t\t// URL without pushing to history.\n\t\t\telse if( config.hash ) {\n\t\t\t\t// If the hash is empty, don't add it to the URL\n\t\t\t\tif( hash === '/' ) {\n\t\t\t\t\tthis.debouncedReplaceState( window.location.pathname + window.location.search );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tthis.debouncedReplaceState( '#' + hash );\n\t\t\t\t}\n\t\t\t}\n\t\t\t// UPDATE: The below nuking of all hash changes breaks\n\t\t\t// anchors on pages where reveal.js is running. Removed\n\t\t\t// in 4.0. Why was it here in the first place? ¯\\_(ツ)_/¯\n\t\t\t//\n\t\t\t// If history and hash are both disabled, a hash may still\n\t\t\t// be added to the URL by clicking on a href with a hash\n\t\t\t// target. Counter this by always removing the hash.\n\t\t\t// else {\n\t\t\t// \twindow.history.replaceState( null, null, window.location.pathname + window.location.search );\n\t\t\t// }\n\n\t\t}\n\n\t}\n\n\treplaceState( url ) {\n\n\t\twindow.history.replaceState( null, null, url );\n\t\tthis.replaceStateTimestamp = Date.now();\n\n\t}\n\n\tdebouncedReplaceState( url ) {\n\n\t\tclearTimeout( this.replaceStateTimeout );\n\n\t\tif( Date.now() - this.replaceStateTimestamp > this.MAX_REPLACE_STATE_FREQUENCY ) {\n\t\t\tthis.replaceState( url );\n\t\t}\n\t\telse {\n\t\t\tthis.replaceStateTimeout = setTimeout( () => this.replaceState( url ), this.MAX_REPLACE_STATE_FREQUENCY );\n\t\t}\n\n\t}\n\n\t/**\n\t * Return a hash URL that will resolve to the given slide location.\n\t *\n\t * @param {HTMLElement} [slide=currentSlide] The slide to link to\n\t */\n\tgetHash( slide ) {\n\n\t\tlet url = '/';\n\n\t\t// Attempt to create a named link based on the slide's ID\n\t\tlet s = slide || this.Reveal.getCurrentSlide();\n\t\tlet id = s ? s.getAttribute( 'id' ) : null;\n\t\tif( id ) {\n\t\t\tid = encodeURIComponent( id );\n\t\t}\n\n\t\tlet index = this.Reveal.getIndices( slide );\n\t\tif( !this.Reveal.getConfig().fragmentInURL ) {\n\t\t\tindex.f = undefined;\n\t\t}\n\n\t\t// If the current slide has an ID, use that as a named link,\n\t\t// but we don't support named links with a fragment index\n\t\tif( typeof id === 'string' && id.length ) {\n\t\t\turl = '/' + id;\n\n\t\t\t// If there is also a fragment, append that at the end\n\t\t\t// of the named link, like: #/named-link/2\n\t\t\tif( index.f >= 0 ) url += '/' + index.f;\n\t\t}\n\t\t// Otherwise use the /h/v index\n\t\telse {\n\t\t\tlet hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0;\n\t\t\tif( index.h > 0 || index.v > 0 || index.f >= 0 ) url += index.h + hashIndexBase;\n\t\t\tif( index.v > 0 || index.f >= 0 ) url += '/' + (index.v + hashIndexBase );\n\t\t\tif( index.f >= 0 ) url += '/' + index.f;\n\t\t}\n\n\t\treturn url;\n\n\t}\n\n\t/**\n\t * Handler for the window level 'hashchange' event.\n\t *\n\t * @param {object} [event]\n\t */\n\tonWindowHashChange( event ) {\n\n\t\tthis.readURL();\n\n\t}\n\n}","import { queryAll } from '../utils/util.js'\nimport { isAndroid } from '../utils/device.js'\n\n/**\n * Manages our presentation controls. This includes both\n * the built-in control arrows as well as event monitoring\n * of any elements within the presentation with either of the\n * following helper classes:\n * - .navigate-up\n * - .navigate-right\n * - .navigate-down\n * - .navigate-left\n * - .navigate-next\n * - .navigate-prev\n */\nexport default class Controls {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\tthis.onNavigateLeftClicked = this.onNavigateLeftClicked.bind( this );\n\t\tthis.onNavigateRightClicked = this.onNavigateRightClicked.bind( this );\n\t\tthis.onNavigateUpClicked = this.onNavigateUpClicked.bind( this );\n\t\tthis.onNavigateDownClicked = this.onNavigateDownClicked.bind( this );\n\t\tthis.onNavigatePrevClicked = this.onNavigatePrevClicked.bind( this );\n\t\tthis.onNavigateNextClicked = this.onNavigateNextClicked.bind( this );\n\n\t}\n\n\trender() {\n\n\t\tconst rtl = this.Reveal.getConfig().rtl;\n\t\tconst revealElement = this.Reveal.getRevealElement();\n\n\t\tthis.element = document.createElement( 'aside' );\n\t\tthis.element.className = 'controls';\n\t\tthis.element.innerHTML =\n\t\t\t`\n\t\t\t\n\t\t\t\n\t\t\t`;\n\n\t\tthis.Reveal.getRevealElement().appendChild( this.element );\n\n\t\t// There can be multiple instances of controls throughout the page\n\t\tthis.controlsLeft = queryAll( revealElement, '.navigate-left' );\n\t\tthis.controlsRight = queryAll( revealElement, '.navigate-right' );\n\t\tthis.controlsUp = queryAll( revealElement, '.navigate-up' );\n\t\tthis.controlsDown = queryAll( revealElement, '.navigate-down' );\n\t\tthis.controlsPrev = queryAll( revealElement, '.navigate-prev' );\n\t\tthis.controlsNext = queryAll( revealElement, '.navigate-next' );\n\n\t\t// The left, right and down arrows in the standard reveal.js controls\n\t\tthis.controlsRightArrow = this.element.querySelector( '.navigate-right' );\n\t\tthis.controlsLeftArrow = this.element.querySelector( '.navigate-left' );\n\t\tthis.controlsDownArrow = this.element.querySelector( '.navigate-down' );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tthis.element.style.display = config.controls ? 'block' : 'none';\n\n\t\tthis.element.setAttribute( 'data-controls-layout', config.controlsLayout );\n\t\tthis.element.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );\n\n\t}\n\n\tbind() {\n\n\t\t// Listen to both touch and click events, in case the device\n\t\t// supports both\n\t\tlet pointerEvents = [ 'touchstart', 'click' ];\n\n\t\t// Only support touch for Android, fixes double navigations in\n\t\t// stock browser\n\t\tif( isAndroid ) {\n\t\t\tpointerEvents = [ 'touchstart' ];\n\t\t}\n\n\t\tpointerEvents.forEach( eventName => {\n\t\t\tthis.controlsLeft.forEach( el => el.addEventListener( eventName, this.onNavigateLeftClicked, false ) );\n\t\t\tthis.controlsRight.forEach( el => el.addEventListener( eventName, this.onNavigateRightClicked, false ) );\n\t\t\tthis.controlsUp.forEach( el => el.addEventListener( eventName, this.onNavigateUpClicked, false ) );\n\t\t\tthis.controlsDown.forEach( el => el.addEventListener( eventName, this.onNavigateDownClicked, false ) );\n\t\t\tthis.controlsPrev.forEach( el => el.addEventListener( eventName, this.onNavigatePrevClicked, false ) );\n\t\t\tthis.controlsNext.forEach( el => el.addEventListener( eventName, this.onNavigateNextClicked, false ) );\n\t\t} );\n\n\t}\n\n\tunbind() {\n\n\t\t[ 'touchstart', 'click' ].forEach( eventName => {\n\t\t\tthis.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );\n\t\t\tthis.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );\n\t\t\tthis.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );\n\t\t\tthis.controlsDown.forEach( el => el.removeEventListener( eventName, this.onNavigateDownClicked, false ) );\n\t\t\tthis.controlsPrev.forEach( el => el.removeEventListener( eventName, this.onNavigatePrevClicked, false ) );\n\t\t\tthis.controlsNext.forEach( el => el.removeEventListener( eventName, this.onNavigateNextClicked, false ) );\n\t\t} );\n\n\t}\n\n\t/**\n\t * Updates the state of all control/navigation arrows.\n\t */\n\tupdate() {\n\n\t\tlet routes = this.Reveal.availableRoutes();\n\n\t\t// Remove the 'enabled' class from all directions\n\t\t[...this.controlsLeft, ...this.controlsRight, ...this.controlsUp, ...this.controlsDown, ...this.controlsPrev, ...this.controlsNext].forEach( node => {\n\t\t\tnode.classList.remove( 'enabled', 'fragmented' );\n\n\t\t\t// Set 'disabled' attribute on all directions\n\t\t\tnode.setAttribute( 'disabled', 'disabled' );\n\t\t} );\n\n\t\t// Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons\n\t\tif( routes.left ) this.controlsLeft.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\tif( routes.right ) this.controlsRight.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\tif( routes.up ) this.controlsUp.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\tif( routes.down ) this.controlsDown.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\n\t\t// Prev/next buttons\n\t\tif( routes.left || routes.up ) this.controlsPrev.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\tif( routes.right || routes.down ) this.controlsNext.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\n\t\t// Highlight fragment directions\n\t\tlet currentSlide = this.Reveal.getCurrentSlide();\n\t\tif( currentSlide ) {\n\n\t\t\tlet fragmentsRoutes = this.Reveal.fragments.availableRoutes();\n\n\t\t\t// Always apply fragment decorator to prev/next buttons\n\t\t\tif( fragmentsRoutes.prev ) this.controlsPrev.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\t\tif( fragmentsRoutes.next ) this.controlsNext.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\n\t\t\t// Apply fragment decorators to directional buttons based on\n\t\t\t// what slide axis they are in\n\t\t\tif( this.Reveal.isVerticalSlide( currentSlide ) ) {\n\t\t\t\tif( fragmentsRoutes.prev ) this.controlsUp.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\t\t\tif( fragmentsRoutes.next ) this.controlsDown.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif( fragmentsRoutes.prev ) this.controlsLeft.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\t\t\tif( fragmentsRoutes.next ) this.controlsRight.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );\n\t\t\t}\n\n\t\t}\n\n\t\tif( this.Reveal.getConfig().controlsTutorial ) {\n\n\t\t\tlet indices = this.Reveal.getIndices();\n\n\t\t\t// Highlight control arrows with an animation to ensure\n\t\t\t// that the viewer knows how to navigate\n\t\t\tif( !this.Reveal.hasNavigatedVertically() && routes.down ) {\n\t\t\t\tthis.controlsDownArrow.classList.add( 'highlight' );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis.controlsDownArrow.classList.remove( 'highlight' );\n\n\t\t\t\tif( this.Reveal.getConfig().rtl ) {\n\n\t\t\t\t\tif( !this.Reveal.hasNavigatedHorizontally() && routes.left && indices.v === 0 ) {\n\t\t\t\t\t\tthis.controlsLeftArrow.classList.add( 'highlight' );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.controlsLeftArrow.classList.remove( 'highlight' );\n\t\t\t\t\t}\n\n\t\t\t\t} else {\n\n\t\t\t\t\tif( !this.Reveal.hasNavigatedHorizontally() && routes.right && indices.v === 0 ) {\n\t\t\t\t\t\tthis.controlsRightArrow.classList.add( 'highlight' );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.controlsRightArrow.classList.remove( 'highlight' );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tdestroy() {\n\n\t\tthis.unbind();\n\t\tthis.element.remove();\n\n\t}\n\n\t/**\n\t * Event handlers for navigation control buttons.\n\t */\n\tonNavigateLeftClicked( event ) {\n\n\t\tevent.preventDefault();\n\t\tthis.Reveal.onUserInput();\n\n\t\tif( this.Reveal.getConfig().navigationMode === 'linear' ) {\n\t\t\tthis.Reveal.prev();\n\t\t}\n\t\telse {\n\t\t\tthis.Reveal.left();\n\t\t}\n\n\t}\n\n\tonNavigateRightClicked( event ) {\n\n\t\tevent.preventDefault();\n\t\tthis.Reveal.onUserInput();\n\n\t\tif( this.Reveal.getConfig().navigationMode === 'linear' ) {\n\t\t\tthis.Reveal.next();\n\t\t}\n\t\telse {\n\t\t\tthis.Reveal.right();\n\t\t}\n\n\t}\n\n\tonNavigateUpClicked( event ) {\n\n\t\tevent.preventDefault();\n\t\tthis.Reveal.onUserInput();\n\n\t\tthis.Reveal.up();\n\n\t}\n\n\tonNavigateDownClicked( event ) {\n\n\t\tevent.preventDefault();\n\t\tthis.Reveal.onUserInput();\n\n\t\tthis.Reveal.down();\n\n\t}\n\n\tonNavigatePrevClicked( event ) {\n\n\t\tevent.preventDefault();\n\t\tthis.Reveal.onUserInput();\n\n\t\tthis.Reveal.prev();\n\n\t}\n\n\tonNavigateNextClicked( event ) {\n\n\t\tevent.preventDefault();\n\t\tthis.Reveal.onUserInput();\n\n\t\tthis.Reveal.next();\n\n\t}\n\n\n}","/**\n * Creates a visual progress bar for the presentation.\n */\nexport default class Progress {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\tthis.onProgressClicked = this.onProgressClicked.bind( this );\n\n\t}\n\n\trender() {\n\n\t\tthis.element = document.createElement( 'div' );\n\t\tthis.element.className = 'progress';\n\t\tthis.Reveal.getRevealElement().appendChild( this.element );\n\n\t\tthis.bar = document.createElement( 'span' );\n\t\tthis.element.appendChild( this.bar );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tthis.element.style.display = config.progress ? 'block' : 'none';\n\n\t}\n\n\tbind() {\n\n\t\tif( this.Reveal.getConfig().progress && this.element ) {\n\t\t\tthis.element.addEventListener( 'click', this.onProgressClicked, false );\n\t\t}\n\n\t}\n\n\tunbind() {\n\n\t\tif ( this.Reveal.getConfig().progress && this.element ) {\n\t\t\tthis.element.removeEventListener( 'click', this.onProgressClicked, false );\n\t\t}\n\n\t}\n\n\t/**\n\t * Updates the progress bar to reflect the current slide.\n\t */\n\tupdate() {\n\n\t\t// Update progress if enabled\n\t\tif( this.Reveal.getConfig().progress && this.bar ) {\n\n\t\t\tlet scale = this.Reveal.getProgress();\n\n\t\t\t// Don't fill the progress bar if there's only one slide\n\t\t\tif( this.Reveal.getTotalSlides() < 2 ) {\n\t\t\t\tscale = 0;\n\t\t\t}\n\n\t\t\tthis.bar.style.transform = 'scaleX('+ scale +')';\n\n\t\t}\n\n\t}\n\n\tgetMaxWidth() {\n\n\t\treturn this.Reveal.getRevealElement().offsetWidth;\n\n\t}\n\n\t/**\n\t * Clicking on the progress bar results in a navigation to the\n\t * closest approximate horizontal slide using this equation:\n\t *\n\t * ( clickX / presentationWidth ) * numberOfSlides\n\t *\n\t * @param {object} event\n\t */\n\tonProgressClicked( event ) {\n\n\t\tthis.Reveal.onUserInput( event );\n\n\t\tevent.preventDefault();\n\n\t\tlet slides = this.Reveal.getSlides();\n\t\tlet slidesTotal = slides.length;\n\t\tlet slideIndex = Math.floor( ( event.clientX / this.getMaxWidth() ) * slidesTotal );\n\n\t\tif( this.Reveal.getConfig().rtl ) {\n\t\t\tslideIndex = slidesTotal - slideIndex;\n\t\t}\n\n\t\tlet targetIndices = this.Reveal.getIndices(slides[slideIndex]);\n\t\tthis.Reveal.slide( targetIndices.h, targetIndices.v );\n\n\t}\n\n\tdestroy() {\n\n\t\tthis.element.remove();\n\n\t}\n\n}","/**\n * Handles hiding of the pointer/cursor when inactive.\n */\nexport default class Pointer {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\t// Throttles mouse wheel navigation\n\t\tthis.lastMouseWheelStep = 0;\n\n\t\t// Is the mouse pointer currently hidden from view\n\t\tthis.cursorHidden = false;\n\n\t\t// Timeout used to determine when the cursor is inactive\n\t\tthis.cursorInactiveTimeout = 0;\n\n\t\tthis.onDocumentCursorActive = this.onDocumentCursorActive.bind( this );\n\t\tthis.onDocumentMouseScroll = this.onDocumentMouseScroll.bind( this );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tif( config.mouseWheel ) {\n\t\t\tdocument.addEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false ); // FF\n\t\t\tdocument.addEventListener( 'mousewheel', this.onDocumentMouseScroll, false );\n\t\t}\n\t\telse {\n\t\t\tdocument.removeEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false ); // FF\n\t\t\tdocument.removeEventListener( 'mousewheel', this.onDocumentMouseScroll, false );\n\t\t}\n\n\t\t// Auto-hide the mouse pointer when its inactive\n\t\tif( config.hideInactiveCursor ) {\n\t\t\tdocument.addEventListener( 'mousemove', this.onDocumentCursorActive, false );\n\t\t\tdocument.addEventListener( 'mousedown', this.onDocumentCursorActive, false );\n\t\t}\n\t\telse {\n\t\t\tthis.showCursor();\n\n\t\t\tdocument.removeEventListener( 'mousemove', this.onDocumentCursorActive, false );\n\t\t\tdocument.removeEventListener( 'mousedown', this.onDocumentCursorActive, false );\n\t\t}\n\n\t}\n\n\t/**\n\t * Shows the mouse pointer after it has been hidden with\n\t * #hideCursor.\n\t */\n\tshowCursor() {\n\n\t\tif( this.cursorHidden ) {\n\t\t\tthis.cursorHidden = false;\n\t\t\tthis.Reveal.getRevealElement().style.cursor = '';\n\t\t}\n\n\t}\n\n\t/**\n\t * Hides the mouse pointer when it's on top of the .reveal\n\t * container.\n\t */\n\thideCursor() {\n\n\t\tif( this.cursorHidden === false ) {\n\t\t\tthis.cursorHidden = true;\n\t\t\tthis.Reveal.getRevealElement().style.cursor = 'none';\n\t\t}\n\n\t}\n\n\tdestroy() {\n\n\t\tthis.showCursor();\n\n\t\tdocument.removeEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false );\n\t\tdocument.removeEventListener( 'mousewheel', this.onDocumentMouseScroll, false );\n\t\tdocument.removeEventListener( 'mousemove', this.onDocumentCursorActive, false );\n\t\tdocument.removeEventListener( 'mousedown', this.onDocumentCursorActive, false );\n\n\t}\n\n\t/**\n\t * Called whenever there is mouse input at the document level\n\t * to determine if the cursor is active or not.\n\t *\n\t * @param {object} event\n\t */\n\tonDocumentCursorActive( event ) {\n\n\t\tthis.showCursor();\n\n\t\tclearTimeout( this.cursorInactiveTimeout );\n\n\t\tthis.cursorInactiveTimeout = setTimeout( this.hideCursor.bind( this ), this.Reveal.getConfig().hideCursorTime );\n\n\t}\n\n\t/**\n\t * Handles mouse wheel scrolling, throttled to avoid skipping\n\t * multiple slides.\n\t *\n\t * @param {object} event\n\t */\n\tonDocumentMouseScroll( event ) {\n\n\t\tif( Date.now() - this.lastMouseWheelStep > 1000 ) {\n\n\t\t\tthis.lastMouseWheelStep = Date.now();\n\n\t\t\tlet delta = event.detail || -event.wheelDelta;\n\t\t\tif( delta > 0 ) {\n\t\t\t\tthis.Reveal.next();\n\t\t\t}\n\t\t\telse if( delta < 0 ) {\n\t\t\t\tthis.Reveal.prev();\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}","/**\n * Loads a JavaScript file from the given URL and executes it.\n *\n * @param {string} url Address of the .js file to load\n * @param {function} callback Method to invoke when the script\n * has loaded and executed\n */\nexport const loadScript = ( url, callback ) => {\n\n\tconst script = document.createElement( 'script' );\n\tscript.type = 'text/javascript';\n\tscript.async = false;\n\tscript.defer = false;\n\tscript.src = url;\n\n\tif( typeof callback === 'function' ) {\n\n\t\t// Success callback\n\t\tscript.onload = script.onreadystatechange = event => {\n\t\t\tif( event.type === 'load' || /loaded|complete/.test( script.readyState ) ) {\n\n\t\t\t\t// Kill event listeners\n\t\t\t\tscript.onload = script.onreadystatechange = script.onerror = null;\n\n\t\t\t\tcallback();\n\n\t\t\t}\n\t\t};\n\n\t\t// Error callback\n\t\tscript.onerror = err => {\n\n\t\t\t// Kill event listeners\n\t\t\tscript.onload = script.onreadystatechange = script.onerror = null;\n\n\t\t\tcallback( new Error( 'Failed loading script: ' + script.src + '\\n' + err ) );\n\n\t\t};\n\n\t}\n\n\t// Append the script at the end of \n\tconst head = document.querySelector( 'head' );\n\thead.insertBefore( script, head.lastChild );\n\n}","import { loadScript } from '../utils/loader.js'\n\n/**\n * Manages loading and registering of reveal.js plugins.\n */\nexport default class Plugins {\n\n\tconstructor( reveal ) {\n\n\t\tthis.Reveal = reveal;\n\n\t\t// Flags our current state (idle -> loading -> loaded)\n\t\tthis.state = 'idle';\n\n\t\t// An id:instance map of currently registed plugins\n\t\tthis.registeredPlugins = {};\n\n\t\tthis.asyncDependencies = [];\n\n\t}\n\n\t/**\n\t * Loads reveal.js dependencies, registers and\n\t * initializes plugins.\n\t *\n\t * Plugins are direct references to a reveal.js plugin\n\t * object that we register and initialize after any\n\t * synchronous dependencies have loaded.\n\t *\n\t * Dependencies are defined via the 'dependencies' config\n\t * option and will be loaded prior to starting reveal.js.\n\t * Some dependencies may have an 'async' flag, if so they\n\t * will load after reveal.js has been started up.\n\t */\n\tload( plugins, dependencies ) {\n\n\t\tthis.state = 'loading';\n\n\t\tplugins.forEach( this.registerPlugin.bind( this ) );\n\n\t\treturn new Promise( resolve => {\n\n\t\t\tlet scripts = [],\n\t\t\t\tscriptsToLoad = 0;\n\n\t\t\tdependencies.forEach( s => {\n\t\t\t\t// Load if there's no condition or the condition is truthy\n\t\t\t\tif( !s.condition || s.condition() ) {\n\t\t\t\t\tif( s.async ) {\n\t\t\t\t\t\tthis.asyncDependencies.push( s );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tscripts.push( s );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\tif( scripts.length ) {\n\t\t\t\tscriptsToLoad = scripts.length;\n\n\t\t\t\tconst scriptLoadedCallback = (s) => {\n\t\t\t\t\tif( s && typeof s.callback === 'function' ) s.callback();\n\n\t\t\t\t\tif( --scriptsToLoad === 0 ) {\n\t\t\t\t\t\tthis.initPlugins().then( resolve );\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Load synchronous scripts\n\t\t\t\tscripts.forEach( s => {\n\t\t\t\t\tif( typeof s.id === 'string' ) {\n\t\t\t\t\t\tthis.registerPlugin( s );\n\t\t\t\t\t\tscriptLoadedCallback( s );\n\t\t\t\t\t}\n\t\t\t\t\telse if( typeof s.src === 'string' ) {\n\t\t\t\t\t\tloadScript( s.src, () => scriptLoadedCallback(s) );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tconsole.warn( 'Unrecognized plugin format', s );\n\t\t\t\t\t\tscriptLoadedCallback();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis.initPlugins().then( resolve );\n\t\t\t}\n\n\t\t} );\n\n\t}\n\n\t/**\n\t * Initializes our plugins and waits for them to be ready\n\t * before proceeding.\n\t */\n\tinitPlugins() {\n\n\t\treturn new Promise( resolve => {\n\n\t\t\tlet pluginValues = Object.values( this.registeredPlugins );\n\t\t\tlet pluginsToInitialize = pluginValues.length;\n\n\t\t\t// If there are no plugins, skip this step\n\t\t\tif( pluginsToInitialize === 0 ) {\n\t\t\t\tthis.loadAsync().then( resolve );\n\t\t\t}\n\t\t\t// ... otherwise initialize plugins\n\t\t\telse {\n\n\t\t\t\tlet initNextPlugin;\n\n\t\t\t\tlet afterPlugInitialized = () => {\n\t\t\t\t\tif( --pluginsToInitialize === 0 ) {\n\t\t\t\t\t\tthis.loadAsync().then( resolve );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tinitNextPlugin();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tlet i = 0;\n\n\t\t\t\t// Initialize plugins serially\n\t\t\t\tinitNextPlugin = () => {\n\n\t\t\t\t\tlet plugin = pluginValues[i++];\n\n\t\t\t\t\t// If the plugin has an 'init' method, invoke it\n\t\t\t\t\tif( typeof plugin.init === 'function' ) {\n\t\t\t\t\t\tlet promise = plugin.init( this.Reveal );\n\n\t\t\t\t\t\t// If the plugin returned a Promise, wait for it\n\t\t\t\t\t\tif( promise && typeof promise.then === 'function' ) {\n\t\t\t\t\t\t\tpromise.then( afterPlugInitialized );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tafterPlugInitialized();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tafterPlugInitialized();\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\tinitNextPlugin();\n\n\t\t\t}\n\n\t\t} )\n\n\t}\n\n\t/**\n\t * Loads all async reveal.js dependencies.\n\t */\n\tloadAsync() {\n\n\t\tthis.state = 'loaded';\n\n\t\tif( this.asyncDependencies.length ) {\n\t\t\tthis.asyncDependencies.forEach( s => {\n\t\t\t\tloadScript( s.src, s.callback );\n\t\t\t} );\n\t\t}\n\n\t\treturn Promise.resolve();\n\n\t}\n\n\t/**\n\t * Registers a new plugin with this reveal.js instance.\n\t *\n\t * reveal.js waits for all regisered plugins to initialize\n\t * before considering itself ready, as long as the plugin\n\t * is registered before calling `Reveal.initialize()`.\n\t */\n\tregisterPlugin( plugin ) {\n\n\t\t// Backwards compatibility to make reveal.js ~3.9.0\n\t\t// plugins work with reveal.js 4.0.0\n\t\tif( arguments.length === 2 && typeof arguments[0] === 'string' ) {\n\t\t\tplugin = arguments[1];\n\t\t\tplugin.id = arguments[0];\n\t\t}\n\t\t// Plugin can optionally be a function which we call\n\t\t// to create an instance of the plugin\n\t\telse if( typeof plugin === 'function' ) {\n\t\t\tplugin = plugin();\n\t\t}\n\n\t\tlet id = plugin.id;\n\n\t\tif( typeof id !== 'string' ) {\n\t\t\tconsole.warn( 'Unrecognized plugin format; can\\'t find plugin.id', plugin );\n\t\t}\n\t\telse if( this.registeredPlugins[id] === undefined ) {\n\t\t\tthis.registeredPlugins[id] = plugin;\n\n\t\t\t// If a plugin is registered after reveal.js is loaded,\n\t\t\t// initialize it right away\n\t\t\tif( this.state === 'loaded' && typeof plugin.init === 'function' ) {\n\t\t\t\tplugin.init( this.Reveal );\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tconsole.warn( 'reveal.js: \"'+ id +'\" plugin has already been registered' );\n\t\t}\n\n\t}\n\n\t/**\n\t * Checks if a specific plugin has been registered.\n\t *\n\t * @param {String} id Unique plugin identifier\n\t */\n\thasPlugin( id ) {\n\n\t\treturn !!this.registeredPlugins[id];\n\n\t}\n\n\t/**\n\t * Returns the specific plugin instance, if a plugin\n\t * with the given ID has been registered.\n\t *\n\t * @param {String} id Unique plugin identifier\n\t */\n\tgetPlugin( id ) {\n\n\t\treturn this.registeredPlugins[id];\n\n\t}\n\n\tgetRegisteredPlugins() {\n\n\t\treturn this.registeredPlugins;\n\n\t}\n\n\tdestroy() {\n\n\t\tObject.values( this.registeredPlugins ).forEach( plugin => {\n\t\t\tif( typeof plugin.destroy === 'function' ) {\n\t\t\t\tplugin.destroy();\n\t\t\t}\n\t\t} );\n\n\t\tthis.registeredPlugins = {};\n\t\tthis.asyncDependencies = [];\n\n\t}\n\n}\n","import { SLIDES_SELECTOR } from '../utils/constants.js'\nimport { queryAll, createStyleSheet } from '../utils/util.js'\n\n/**\n * Setups up our presentation for printing/exporting to PDF.\n */\nexport default class Print {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t}\n\n\t/**\n\t * Configures the presentation for printing to a static\n\t * PDF.\n\t */\n\tasync setupPDF() {\n\n\t\tconst config = this.Reveal.getConfig();\n\t\tconst slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR )\n\n\t\t// Compute slide numbers now, before we start duplicating slides\n\t\tconst doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber );\n\n\t\tconst slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight );\n\n\t\t// Dimensions of the PDF pages\n\t\tconst pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),\n\t\t\tpageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );\n\n\t\t// Dimensions of slides within the pages\n\t\tconst slideWidth = slideSize.width,\n\t\t\tslideHeight = slideSize.height;\n\n\t\tawait new Promise( requestAnimationFrame );\n\n\t\t// Let the browser know what page size we want to print\n\t\tcreateStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );\n\n\t\t// Limit the size of certain elements to the dimensions of the slide\n\t\tcreateStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );\n\n\t\tdocument.documentElement.classList.add( 'print-pdf' );\n\t\tdocument.body.style.width = pageWidth + 'px';\n\t\tdocument.body.style.height = pageHeight + 'px';\n\n\t\tconst viewportElement = document.querySelector( '.reveal-viewport' );\n\t\tlet presentationBackground;\n\t\tif( viewportElement ) {\n\t\t\tconst viewportStyles = window.getComputedStyle( viewportElement );\n\t\t\tif( viewportStyles && viewportStyles.background ) {\n\t\t\t\tpresentationBackground = viewportStyles.background;\n\t\t\t}\n\t\t}\n\n\t\t// Make sure stretch elements fit on slide\n\t\tawait new Promise( requestAnimationFrame );\n\t\tthis.Reveal.layoutSlideContents( slideWidth, slideHeight );\n\n\t\t// Batch scrollHeight access to prevent layout thrashing\n\t\tawait new Promise( requestAnimationFrame );\n\n\t\tconst slideScrollHeights = slides.map( slide => slide.scrollHeight );\n\n\t\tconst pages = [];\n\t\tconst pageContainer = slides[0].parentNode;\n\n\t\t// Slide and slide background layout\n\t\tslides.forEach( function( slide, index ) {\n\n\t\t\t// Vertical stacks are not centred since their section\n\t\t\t// children will be\n\t\t\tif( slide.classList.contains( 'stack' ) === false ) {\n\t\t\t\t// Center the slide inside of the page, giving the slide some margin\n\t\t\t\tlet left = ( pageWidth - slideWidth ) / 2;\n\t\t\t\tlet top = ( pageHeight - slideHeight ) / 2;\n\n\t\t\t\tconst contentHeight = slideScrollHeights[ index ];\n\t\t\t\tlet numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );\n\n\t\t\t\t// Adhere to configured pages per slide limit\n\t\t\t\tnumberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );\n\n\t\t\t\t// Center slides vertically\n\t\t\t\tif( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {\n\t\t\t\t\ttop = Math.max( ( pageHeight - contentHeight ) / 2, 0 );\n\t\t\t\t}\n\n\t\t\t\t// Wrap the slide in a page element and hide its overflow\n\t\t\t\t// so that no page ever flows onto another\n\t\t\t\tconst page = document.createElement( 'div' );\n\t\t\t\tpages.push( page );\n\n\t\t\t\tpage.className = 'pdf-page';\n\t\t\t\tpage.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';\n\n\t\t\t\t// Copy the presentation-wide background to each individual\n\t\t\t\t// page when printing\n\t\t\t\tif( presentationBackground ) {\n\t\t\t\t\tpage.style.background = presentationBackground;\n\t\t\t\t}\n\n\t\t\t\tpage.appendChild( slide );\n\n\t\t\t\t// Position the slide inside of the page\n\t\t\t\tslide.style.left = left + 'px';\n\t\t\t\tslide.style.top = top + 'px';\n\t\t\t\tslide.style.width = slideWidth + 'px';\n\n\t\t\t\t// Re-run the slide layout so that r-fit-text is applied based on\n\t\t\t\t// the printed slide size\n\t\t\t\tthis.Reveal.slideContent.layout( slide )\n\n\t\t\t\tif( slide.slideBackgroundElement ) {\n\t\t\t\t\tpage.insertBefore( slide.slideBackgroundElement, slide );\n\t\t\t\t}\n\n\t\t\t\t// Inject notes if `showNotes` is enabled\n\t\t\t\tif( config.showNotes ) {\n\n\t\t\t\t\t// Are there notes for this slide?\n\t\t\t\t\tconst notes = this.Reveal.getSlideNotes( slide );\n\t\t\t\t\tif( notes ) {\n\n\t\t\t\t\t\tconst notesSpacing = 8;\n\t\t\t\t\t\tconst notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';\n\t\t\t\t\t\tconst notesElement = document.createElement( 'div' );\n\t\t\t\t\t\tnotesElement.classList.add( 'speaker-notes' );\n\t\t\t\t\t\tnotesElement.classList.add( 'speaker-notes-pdf' );\n\t\t\t\t\t\tnotesElement.setAttribute( 'data-layout', notesLayout );\n\t\t\t\t\t\tnotesElement.innerHTML = notes;\n\n\t\t\t\t\t\tif( notesLayout === 'separate-page' ) {\n\t\t\t\t\t\t\tpages.push( notesElement );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tnotesElement.style.left = notesSpacing + 'px';\n\t\t\t\t\t\t\tnotesElement.style.bottom = notesSpacing + 'px';\n\t\t\t\t\t\t\tnotesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';\n\t\t\t\t\t\t\tpage.appendChild( notesElement );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\t// Inject slide numbers if `slideNumbers` are enabled\n\t\t\t\tif( doingSlideNumbers ) {\n\t\t\t\t\tconst slideNumber = index + 1;\n\t\t\t\t\tconst numberElement = document.createElement( 'div' );\n\t\t\t\t\tnumberElement.classList.add( 'slide-number' );\n\t\t\t\t\tnumberElement.classList.add( 'slide-number-pdf' );\n\t\t\t\t\tnumberElement.innerHTML = slideNumber;\n\t\t\t\t\tpage.appendChild( numberElement );\n\t\t\t\t}\n\n\t\t\t\t// Copy page and show fragments one after another\n\t\t\t\tif( config.pdfSeparateFragments ) {\n\n\t\t\t\t\t// Each fragment 'group' is an array containing one or more\n\t\t\t\t\t// fragments. Multiple fragments that appear at the same time\n\t\t\t\t\t// are part of the same group.\n\t\t\t\t\tconst fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true );\n\n\t\t\t\t\tlet previousFragmentStep;\n\n\t\t\t\t\tfragmentGroups.forEach( function( fragments ) {\n\n\t\t\t\t\t\t// Remove 'current-fragment' from the previous group\n\t\t\t\t\t\tif( previousFragmentStep ) {\n\t\t\t\t\t\t\tpreviousFragmentStep.forEach( function( fragment ) {\n\t\t\t\t\t\t\t\tfragment.classList.remove( 'current-fragment' );\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Show the fragments for the current index\n\t\t\t\t\t\tfragments.forEach( function( fragment ) {\n\t\t\t\t\t\t\tfragment.classList.add( 'visible', 'current-fragment' );\n\t\t\t\t\t\t}, this );\n\n\t\t\t\t\t\t// Create a separate page for the current fragment state\n\t\t\t\t\t\tconst clonedPage = page.cloneNode( true );\n\t\t\t\t\t\tpages.push( clonedPage );\n\n\t\t\t\t\t\tpreviousFragmentStep = fragments;\n\n\t\t\t\t\t}, this );\n\n\t\t\t\t\t// Reset the first/original page so that all fragments are hidden\n\t\t\t\t\tfragmentGroups.forEach( function( fragments ) {\n\t\t\t\t\t\tfragments.forEach( function( fragment ) {\n\t\t\t\t\t\t\tfragment.classList.remove( 'visible', 'current-fragment' );\n\t\t\t\t\t\t} );\n\t\t\t\t\t} );\n\n\t\t\t\t}\n\t\t\t\t// Show all fragments\n\t\t\t\telse {\n\t\t\t\t\tqueryAll( page, '.fragment:not(.fade-out)' ).forEach( function( fragment ) {\n\t\t\t\t\t\tfragment.classList.add( 'visible' );\n\t\t\t\t\t} );\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}, this );\n\n\t\tawait new Promise( requestAnimationFrame );\n\n\t\tpages.forEach( page => pageContainer.appendChild( page ) );\n\n\t\t// Notify subscribers that the PDF layout is good to go\n\t\tthis.Reveal.dispatchEvent({ type: 'pdf-ready' });\n\n\t}\n\n\t/**\n\t * Checks if this instance is being used to print a PDF.\n\t */\n\tisPrintingPDF() {\n\n\t\treturn ( /print-pdf/gi ).test( window.location.search );\n\n\t}\n\n}\n","import { isAndroid } from '../utils/device.js'\nimport { matches } from '../utils/util.js'\n\nconst SWIPE_THRESHOLD = 40;\n\n/**\n * Controls all touch interactions and navigations for\n * a presentation.\n */\nexport default class Touch {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\t// Holds information about the currently ongoing touch interaction\n\t\tthis.touchStartX = 0;\n\t\tthis.touchStartY = 0;\n\t\tthis.touchStartCount = 0;\n\t\tthis.touchCaptured = false;\n\n\t\tthis.onPointerDown = this.onPointerDown.bind( this );\n\t\tthis.onPointerMove = this.onPointerMove.bind( this );\n\t\tthis.onPointerUp = this.onPointerUp.bind( this );\n\t\tthis.onTouchStart = this.onTouchStart.bind( this );\n\t\tthis.onTouchMove = this.onTouchMove.bind( this );\n\t\tthis.onTouchEnd = this.onTouchEnd.bind( this );\n\n\t}\n\n\t/**\n\t *\n\t */\n\tbind() {\n\n\t\tlet revealElement = this.Reveal.getRevealElement();\n\n\t\tif( 'onpointerdown' in window ) {\n\t\t\t// Use W3C pointer events\n\t\t\trevealElement.addEventListener( 'pointerdown', this.onPointerDown, false );\n\t\t\trevealElement.addEventListener( 'pointermove', this.onPointerMove, false );\n\t\t\trevealElement.addEventListener( 'pointerup', this.onPointerUp, false );\n\t\t}\n\t\telse if( window.navigator.msPointerEnabled ) {\n\t\t\t// IE 10 uses prefixed version of pointer events\n\t\t\trevealElement.addEventListener( 'MSPointerDown', this.onPointerDown, false );\n\t\t\trevealElement.addEventListener( 'MSPointerMove', this.onPointerMove, false );\n\t\t\trevealElement.addEventListener( 'MSPointerUp', this.onPointerUp, false );\n\t\t}\n\t\telse {\n\t\t\t// Fall back to touch events\n\t\t\trevealElement.addEventListener( 'touchstart', this.onTouchStart, false );\n\t\t\trevealElement.addEventListener( 'touchmove', this.onTouchMove, false );\n\t\t\trevealElement.addEventListener( 'touchend', this.onTouchEnd, false );\n\t\t}\n\n\t}\n\n\t/**\n\t *\n\t */\n\tunbind() {\n\n\t\tlet revealElement = this.Reveal.getRevealElement();\n\n\t\trevealElement.removeEventListener( 'pointerdown', this.onPointerDown, false );\n\t\trevealElement.removeEventListener( 'pointermove', this.onPointerMove, false );\n\t\trevealElement.removeEventListener( 'pointerup', this.onPointerUp, false );\n\n\t\trevealElement.removeEventListener( 'MSPointerDown', this.onPointerDown, false );\n\t\trevealElement.removeEventListener( 'MSPointerMove', this.onPointerMove, false );\n\t\trevealElement.removeEventListener( 'MSPointerUp', this.onPointerUp, false );\n\n\t\trevealElement.removeEventListener( 'touchstart', this.onTouchStart, false );\n\t\trevealElement.removeEventListener( 'touchmove', this.onTouchMove, false );\n\t\trevealElement.removeEventListener( 'touchend', this.onTouchEnd, false );\n\n\t}\n\n\t/**\n\t * Checks if the target element prevents the triggering of\n\t * swipe navigation.\n\t */\n\tisSwipePrevented( target ) {\n\n\t\t// Prevent accidental swipes when scrubbing timelines\n\t\tif( matches( target, 'video, audio' ) ) return true;\n\n\t\twhile( target && typeof target.hasAttribute === 'function' ) {\n\t\t\tif( target.hasAttribute( 'data-prevent-swipe' ) ) return true;\n\t\t\ttarget = target.parentNode;\n\t\t}\n\n\t\treturn false;\n\n\t}\n\n\t/**\n\t * Handler for the 'touchstart' event, enables support for\n\t * swipe and pinch gestures.\n\t *\n\t * @param {object} event\n\t */\n\tonTouchStart( event ) {\n\n\t\tif( this.isSwipePrevented( event.target ) ) return true;\n\n\t\tthis.touchStartX = event.touches[0].clientX;\n\t\tthis.touchStartY = event.touches[0].clientY;\n\t\tthis.touchStartCount = event.touches.length;\n\n\t}\n\n\t/**\n\t * Handler for the 'touchmove' event.\n\t *\n\t * @param {object} event\n\t */\n\tonTouchMove( event ) {\n\n\t\tif( this.isSwipePrevented( event.target ) ) return true;\n\n\t\tlet config = this.Reveal.getConfig();\n\n\t\t// Each touch should only trigger one action\n\t\tif( !this.touchCaptured ) {\n\t\t\tthis.Reveal.onUserInput( event );\n\n\t\t\tlet currentX = event.touches[0].clientX;\n\t\t\tlet currentY = event.touches[0].clientY;\n\n\t\t\t// There was only one touch point, look for a swipe\n\t\t\tif( event.touches.length === 1 && this.touchStartCount !== 2 ) {\n\n\t\t\t\tlet availableRoutes = this.Reveal.availableRoutes({ includeFragments: true });\n\n\t\t\t\tlet deltaX = currentX - this.touchStartX,\n\t\t\t\t\tdeltaY = currentY - this.touchStartY;\n\n\t\t\t\tif( deltaX > SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {\n\t\t\t\t\tthis.touchCaptured = true;\n\t\t\t\t\tif( config.navigationMode === 'linear' ) {\n\t\t\t\t\t\tif( config.rtl ) {\n\t\t\t\t\t\t\tthis.Reveal.next();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tthis.Reveal.prev();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.Reveal.left();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( deltaX < -SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {\n\t\t\t\t\tthis.touchCaptured = true;\n\t\t\t\t\tif( config.navigationMode === 'linear' ) {\n\t\t\t\t\t\tif( config.rtl ) {\n\t\t\t\t\t\t\tthis.Reveal.prev();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tthis.Reveal.next();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.Reveal.right();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( deltaY > SWIPE_THRESHOLD && availableRoutes.up ) {\n\t\t\t\t\tthis.touchCaptured = true;\n\t\t\t\t\tif( config.navigationMode === 'linear' ) {\n\t\t\t\t\t\tthis.Reveal.prev();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.Reveal.up();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if( deltaY < -SWIPE_THRESHOLD && availableRoutes.down ) {\n\t\t\t\t\tthis.touchCaptured = true;\n\t\t\t\t\tif( config.navigationMode === 'linear' ) {\n\t\t\t\t\t\tthis.Reveal.next();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.Reveal.down();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If we're embedded, only block touch events if they have\n\t\t\t\t// triggered an action\n\t\t\t\tif( config.embedded ) {\n\t\t\t\t\tif( this.touchCaptured || this.Reveal.isVerticalSlide() ) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Not embedded? Block them all to avoid needless tossing\n\t\t\t\t// around of the viewport in iOS\n\t\t\t\telse {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t\t// There's a bug with swiping on some Android devices unless\n\t\t// the default action is always prevented\n\t\telse if( isAndroid ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\n\t}\n\n\t/**\n\t * Handler for the 'touchend' event.\n\t *\n\t * @param {object} event\n\t */\n\tonTouchEnd( event ) {\n\n\t\tthis.touchCaptured = false;\n\n\t}\n\n\t/**\n\t * Convert pointer down to touch start.\n\t *\n\t * @param {object} event\n\t */\n\tonPointerDown( event ) {\n\n\t\tif( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === \"touch\" ) {\n\t\t\tevent.touches = [{ clientX: event.clientX, clientY: event.clientY }];\n\t\t\tthis.onTouchStart( event );\n\t\t}\n\n\t}\n\n\t/**\n\t * Convert pointer move to touch move.\n\t *\n\t * @param {object} event\n\t */\n\tonPointerMove( event ) {\n\n\t\tif( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === \"touch\" ) {\n\t\t\tevent.touches = [{ clientX: event.clientX, clientY: event.clientY }];\n\t\t\tthis.onTouchMove( event );\n\t\t}\n\n\t}\n\n\t/**\n\t * Convert pointer up to touch end.\n\t *\n\t * @param {object} event\n\t */\n\tonPointerUp( event ) {\n\n\t\tif( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === \"touch\" ) {\n\t\t\tevent.touches = [{ clientX: event.clientX, clientY: event.clientY }];\n\t\t\tthis.onTouchEnd( event );\n\t\t}\n\n\t}\n\n}","import { closest } from '../utils/util.js'\n\n/**\n * Manages focus when a presentation is embedded. This\n * helps us only capture keyboard from the presentation\n * a user is currently interacting with in a page where\n * multiple presentations are embedded.\n */\n\nconst STATE_FOCUS = 'focus';\nconst STATE_BLUR = 'blur';\n\nexport default class Focus {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t\tthis.onRevealPointerDown = this.onRevealPointerDown.bind( this );\n\t\tthis.onDocumentPointerDown = this.onDocumentPointerDown.bind( this );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tif( config.embedded ) {\n\t\t\tthis.blur();\n\t\t}\n\t\telse {\n\t\t\tthis.focus();\n\t\t\tthis.unbind();\n\t\t}\n\n\t}\n\n\tbind() {\n\n\t\tif( this.Reveal.getConfig().embedded ) {\n\t\t\tthis.Reveal.getRevealElement().addEventListener( 'pointerdown', this.onRevealPointerDown, false );\n\t\t}\n\n\t}\n\n\tunbind() {\n\n\t\tthis.Reveal.getRevealElement().removeEventListener( 'pointerdown', this.onRevealPointerDown, false );\n\t\tdocument.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false );\n\n\t}\n\n\tfocus() {\n\n\t\tif( this.state !== STATE_FOCUS ) {\n\t\t\tthis.Reveal.getRevealElement().classList.add( 'focused' );\n\t\t\tdocument.addEventListener( 'pointerdown', this.onDocumentPointerDown, false );\n\t\t}\n\n\t\tthis.state = STATE_FOCUS;\n\n\t}\n\n\tblur() {\n\n\t\tif( this.state !== STATE_BLUR ) {\n\t\t\tthis.Reveal.getRevealElement().classList.remove( 'focused' );\n\t\t\tdocument.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false );\n\t\t}\n\n\t\tthis.state = STATE_BLUR;\n\n\t}\n\n\tisFocused() {\n\n\t\treturn this.state === STATE_FOCUS;\n\n\t}\n\n\tdestroy() {\n\n\t\tthis.Reveal.getRevealElement().classList.remove( 'focused' );\n\n\t}\n\n\tonRevealPointerDown( event ) {\n\n\t\tthis.focus();\n\n\t}\n\n\tonDocumentPointerDown( event ) {\n\n\t\tlet revealElement = closest( event.target, '.reveal' );\n\t\tif( !revealElement || revealElement !== this.Reveal.getRevealElement() ) {\n\t\t\tthis.blur();\n\t\t}\n\n\t}\n\n}","/**\n * Handles the showing and \n */\nexport default class Notes {\n\n\tconstructor( Reveal ) {\n\n\t\tthis.Reveal = Reveal;\n\n\t}\n\n\trender() {\n\n\t\tthis.element = document.createElement( 'div' );\n\t\tthis.element.className = 'speaker-notes';\n\t\tthis.element.setAttribute( 'data-prevent-swipe', '' );\n\t\tthis.element.setAttribute( 'tabindex', '0' );\n\t\tthis.Reveal.getRevealElement().appendChild( this.element );\n\n\t}\n\n\t/**\n\t * Called when the reveal.js config is updated.\n\t */\n\tconfigure( config, oldConfig ) {\n\n\t\tif( config.showNotes ) {\n\t\t\tthis.element.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );\n\t\t}\n\n\t}\n\n\t/**\n\t * Pick up notes from the current slide and display them\n\t * to the viewer.\n\t *\n\t * @see {@link config.showNotes}\n\t */\n\tupdate() {\n\n\t\tif( this.Reveal.getConfig().showNotes && this.element && this.Reveal.getCurrentSlide() && !this.Reveal.print.isPrintingPDF() ) {\n\n\t\t\tthis.element.innerHTML = this.getSlideNotes() || 'No notes on this slide.';\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Updates the visibility of the speaker notes sidebar that\n\t * is used to share annotated slides. The notes sidebar is\n\t * only visible if showNotes is true and there are notes on\n\t * one or more slides in the deck.\n\t */\n\tupdateVisibility() {\n\n\t\tif( this.Reveal.getConfig().showNotes && this.hasNotes() && !this.Reveal.print.isPrintingPDF() ) {\n\t\t\tthis.Reveal.getRevealElement().classList.add( 'show-notes' );\n\t\t}\n\t\telse {\n\t\t\tthis.Reveal.getRevealElement().classList.remove( 'show-notes' );\n\t\t}\n\n\t}\n\n\t/**\n\t * Checks if there are speaker notes for ANY slide in the\n\t * presentation.\n\t */\n\thasNotes() {\n\n\t\treturn this.Reveal.getSlidesElement().querySelectorAll( '[data-notes], aside.notes' ).length > 0;\n\n\t}\n\n\t/**\n\t * Checks if this presentation is running inside of the\n\t * speaker notes window.\n\t *\n\t * @return {boolean}\n\t */\n\tisSpeakerNotesWindow() {\n\n\t\treturn !!window.location.search.match( /receiver/gi );\n\n\t}\n\n\t/**\n\t * Retrieves the speaker notes from a slide. Notes can be\n\t * defined in two ways:\n\t * 1. As a data-notes attribute on the slide
    \n\t * 2. As an
    @@ -2568,6 +2569,17 @@

    About the input; testing

    [1] 1.414214
    +
    +
    q <- 17
    +sqrt_minus_1(q)
    +
    +
    [1] 4
    +
    +
    sqrt_minus_1("text")
    +
    +
    Error in x - 1: non-numeric argument to binary operator
    +
    +
    • It works!
    @@ -2578,7 +2590,7 @@

    Vectorization 1/2

  • We conceived our function to work on numbers:
  • -
    sqrt_minus_1(3.25)
    +
    sqrt_minus_1(3.25)
    [1] 1.5
    @@ -2587,7 +2599,7 @@

    Vectorization 1/2

  • but it actually works on vectors too, as a free bonus of R:
  • -
    sqrt_minus_1(c(50, 11, 3))
    +
    sqrt_minus_1(c(50, 11, 3))
    [1] 7.000000 3.162278 1.414214
    @@ -2602,8 +2614,17 @@

    Vectorization 2/2

  • or even data frames:
  • -
    d <- tibble(x = 1:2, y = 3:4)
    -sqrt_minus_1(d)
    +
    d <- data.frame(x = 1:2, y = 3:4)
    +d
    +
    + +
    + +
    +
    +
    sqrt_minus_1(d)
    @@ -2620,15 +2641,15 @@

    More than one input

  • Allow the value to be subtracted, before taking square root, to be input to function as well, thus:
  • -
    sqrt_minus_value <- function(x, d) {
    -  sqrt(x - d)
    -}
    +
    sqrt_minus_value <- function(x, d) {
    +  sqrt(x - d)
    +}
    • Call the function with the x and d inputs in the right order:
    -
    sqrt_minus_value(51, 2)
    +
    sqrt_minus_value(51, 2)
    [1] 7
    @@ -2637,11 +2658,23 @@

    More than one input

  • or give the inputs names, in which case they can be in any order:
  • -
    sqrt_minus_value(d = 2, x = 51)
    +
    sqrt_minus_value(d = 2, x = 51)
    [1] 7
    +
    +
    lm(y ~ x, data = d)
    +
    +
    
    +Call:
    +lm(formula = y ~ x, data = d)
    +
    +Coefficients:
    +(Intercept)            x  
    +          2            1  
    +
    +

    Defaults 1/2

    @@ -2649,12 +2682,12 @@

    Defaults 1/2

  • Many R functions have values that you can change if you want to, but usually you don’t want to, for example:
  • -
    x <- c(3, 4, 5, NA, 6, 7)
    -mean(x)
    +
    x <- c(3, 4, 5, NA, 6, 7)
    +mean(x)
    [1] NA
    -
    mean(x, na.rm = TRUE)
    +
    mean(x, na.rm = TRUE)
    [1] 5
    @@ -2670,19 +2703,19 @@

    Defaults 2/2

  • In our function, set a default value for d like this:
  • -
    sqrt_minus_value <- function(x, d = 1) {
    -  sqrt(x - d)
    -}
    +
    sqrt_minus_value <- function(x, d = 1) {
    +  sqrt(x - d)
    +}
    • If you specify a value for d, it will be used. If you don’t, 1 will be used instead:
    -
    sqrt_minus_value(51, 2)
    +
    sqrt_minus_value(51, 2)
    [1] 7
    -
    sqrt_minus_value(51)
    +
    sqrt_minus_value(51)
    [1] 7.071068
    @@ -2694,7 +2727,7 @@

    Catching errors before they happen

  • What happened here?
  • -
    sqrt_minus_value(6, 8)
    +
    sqrt_minus_value(6, 8)
    Warning in sqrt(x - d): NaNs produced
    @@ -2708,10 +2741,10 @@

    Catching errors before they happen

  • Check that the square root will be OK first. Here’s how:
  • -
    sqrt_minus_value <- function(x, d = 1) {
    -  stopifnot(x - d >= 0)
    -  sqrt(x - d)
    -}
    +
    sqrt_minus_value <- function(x, d = 1) {
    +  stopifnot(x - d >= 0)
    +  sqrt(x - d)
    +}
    @@ -2720,7 +2753,7 @@

    What happens with stopifnot

  • This should be good, and is:
  • -
    sqrt_minus_value(8, 6)
    +
    sqrt_minus_value(8, 6)
    [1] 1.414214
    @@ -2729,7 +2762,7 @@

    What happens with stopifnot

  • This should fail, and see how it does:
  • -
    sqrt_minus_value(6, 8)
    +
    sqrt_minus_value(6, 8)
    Error in sqrt_minus_value(6, 8): x - d >= 0 is not TRUE
    @@ -2746,11 +2779,39 @@

    Using R’s built-ins

  • For example, if you will be calculating a lot of regression-line slopes, you don’t have to do this from scratch: you can use R’s regression calculations, like this:
  • -
    my_df <- tibble(x = 1:4, y = c(10, 11, 10, 14))
    -# my_df
    -my_df.1 <- lm(y ~ x, data = my_df)
    -# summary(my_df.1)
    -tidy(my_df.1)
    +
    my_df <- data.frame(x = 1:4, y = c(10, 11, 10, 14))
    +my_df
    +
    + +
    + +
    +
    +
    my_df.1 <- lm(y ~ x, data = my_df)
    +summary(my_df.1)
    +
    +
    
    +Call:
    +lm(formula = y ~ x, data = my_df)
    +
    +Residuals:
    +   1    2    3    4 
    + 0.4  0.3 -1.8  1.1 
    +
    +Coefficients:
    +            Estimate Std. Error t value Pr(>|t|)  
    +(Intercept)   8.5000     1.8775   4.527   0.0455 *
    +x             1.1000     0.6856   1.605   0.2498  
    +---
    +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 1.533 on 2 degrees of freedom
    +Multiple R-squared:  0.5628,    Adjusted R-squared:  0.3442 
    +F-statistic: 2.574 on 1 and 2 DF,  p-value: 0.2498
    +
    +
    tidy(my_df.1)
    @@ -2765,7 +2826,7 @@

    Using R’s built-ins

    Pulling out just the slope

    Use pluck:

    -
    tidy(my_df.1) %>% pluck("estimate", 2)
    +
    tidy(my_df.1) %>% pluck("estimate", 2)
    [1] 1.1
    @@ -2779,16 +2840,16 @@

    Making this into a function

  • Output: just the slope, a number. Thus:
  • -
    slope <- function(xx, yy) {
    -  y.1 <- lm(yy ~ xx)
    -  tidy(y.1) %>% pluck("estimate", 2)
    -}
    +
    slope <- function(xx, yy) {
    +  y.1 <- lm(yy ~ xx)
    +  tidy(y.1) %>% pluck("estimate", 2)
    +}
    • Check using our data from before: correct:
    -
    with(my_df, slope(x, y))
    +
    with(my_df, slope(x, y))
    [1] 1.1
    @@ -2800,10 +2861,10 @@

    Passing things on

  • lm has a lot of options, with defaults, that we might want to change. Instead of intercepting all the possibilities and passing them on, we can do this:
  • -
    slope <- function(xx, yy, ...) {
    -  y.1 <- lm(yy ~ xx, ...)
    -  tidy(y.1) %>% pluck("estimate", 2)
    -}
    +
    slope <- function(xx, yy, ...) {
    +  y.1 <- lm(yy ~ xx, ...)
    +  tidy(y.1) %>% pluck("estimate", 2)
    +}
    • The ... in the header line means “accept any other input”, and the ... in the lm line means “pass anything other than x and y straight on to lm”.
    • @@ -2816,7 +2877,7 @@

      Using ...

    • So we should be able to do this:
    -
    with(my_df, slope(x, y, subset = 3:4))
    +
    with(my_df, slope(x, y, subset = 3:4))
    [1] 4
    @@ -2825,7 +2886,7 @@

    Using ...

  • Just uses the last two observations in x and y:
  • -
    my_df %>% slice(3:4)
    +
    my_df %>% slice(3:4)
    @@ -2845,7 +2906,7 @@

    Running a function for each of several inputs

  • Suppose we have a data frame containing several different x’s to use in regressions, along with the y we had before:
  • -
    (d <- tibble(x1 = 1:4, x2 = c(8, 7, 6, 5), x3 = c(2, 4, 6, 9)))
    +
    (d <- tibble(x1 = 1:4, x2 = c(8, 7, 6, 5), x3 = c(2, 4, 6, 9)))
    @@ -2869,12 +2930,12 @@

    The loop way

  • Looping variable i goes from 1 to 3 (3 columns, thus 3 slopes):
  • -
    slopes <- numeric(3)
    -for (i in 1:3) {
    -  d %>% pull(i) -> xx
    -  slopes[i] <- slope(xx, my_df$y)
    -}
    -slopes
    +
    slopes <- numeric(3)
    +for (i in 1:3) {
    +  d %>% pull(i) -> xx
    +  slopes[i] <- slope(xx, my_df$y)
    +}
    +slopes
    [1]  1.1000000 -1.1000000  0.5140187
    @@ -2890,7 +2951,7 @@

    The map_dbl way

  • Since slope returns a decimal number (a dbl), appropriate function-running function is map_dbl:
  • -
    map_dbl(d, \(d) slope(d, my_df$y))
    +
    map_dbl(d, \(d) slope(d, my_df$y))
            x1         x2         x3 
      1.1000000 -1.1000000  0.5140187 
    @@ -2906,8 +2967,8 @@

    Square roots

  • “Find the square roots of each of the numbers 1 through 10”:
  • -
    x <- 1:10
    -map_dbl(x, \(x) sqrt(x))
    +
    x <- 1:10
    +map_dbl(x, \(x) sqrt(x))
     [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
      [9] 3.000000 3.162278
    @@ -2920,12 +2981,12 @@

    Summarizing all columns of a data frame, two ways

  • use my d from above:
  • -
    map_dbl(d, \(d) mean(d))
    +
    map_dbl(d, \(d) mean(d))
      x1   x2   x3 
     2.50 6.50 5.25 
    -
    d %>% summarize(across(everything(), \(x) mean(x)))
    +
    d %>% summarize(across(everything(), \(x) mean(x)))
    @@ -2943,10 +3004,10 @@

    What if summary returns more than one thing?

  • For example, finding quartiles:
  • -
    quartiles <- function(x) {
    -  quantile(x, c(0.25, 0.75))
    -}
    -quartiles(1:5)
    +
    quartiles <- function(x) {
    +  quantile(x, c(0.25, 0.75))
    +}
    +quartiles(1:5)
    25% 75% 
       2   4 
    @@ -2962,7 +3023,7 @@

    map results

  • Try:
  • -
    map(d, \(d) quartiles(d))
    +
    map(d, \(d) quartiles(d))
    $x1
      25%  75% 
    @@ -2987,7 +3048,7 @@ 

    Or

  • Better: pretend output from quartiles is one-column data frame:
  • -
    map_df(d, \(d) quartiles(d))
    +
    map_df(d, \(d) quartiles(d))
    @@ -3001,7 +3062,7 @@

    Or

    Or even

    -
    d %>% map_df(\(d) quartiles(d))
    +
    d %>% map_df(\(d) quartiles(d))
    @@ -3025,8 +3086,8 @@

    Map in data frames with mutate

  • map can also be used within data frames to calculate new columns. Let’s do the square roots of 1 through 10 again:
  • -
    d <- tibble(x = 1:10)
    -d %>% mutate(root = map_dbl(x, \(x) sqrt(x)))
    +
    d <- tibble(x = 1:10)
    +d %>% mutate(root = map_dbl(x, \(x) sqrt(x)))
    @@ -3052,11 +3113,11 @@

    Odd or even?

  • Odd or even? Work out the remainder when dividing by 2:
  • -
    6 %% 2
    +
    6 %% 2
    [1] 0
    -
    5 %% 2
    +
    5 %% 2
    [1] 1
    @@ -3071,31 +3132,31 @@

    Write the function

  • First test for integerness, then test for odd or even, and then do the appropriate calculation:
  • -
    hotpo <- function(x) {
    -  stopifnot(round(x) == x) # passes if input an integer
    -  remainder <- x %% 2
    -  if (remainder == 1) {
    -    ans <- 3 * x + 1
    -  }
    -  else {
    -    ans <- x %/% 2 # integer division
    -  }
    -  ans
    -}
    +
    hotpo <- function(x) {
    +  stopifnot(round(x) == x) # passes if input an integer
    +  remainder <- x %% 2
    +  if (remainder == 1) {
    +    ans <- 3 * x + 1
    +  }
    +  else {
    +    ans <- x %/% 2 # integer division
    +  }
    +  ans
    +}

    Test it

    -
    hotpo(3)
    +
    hotpo(3)
    [1] 10
    -
    hotpo(12)
    +
    hotpo(12)
    [1] 6
    -
    hotpo(4.5)
    +
    hotpo(4.5)
    Error in hotpo(4.5): round(x) == x is not TRUE
    @@ -3107,7 +3168,7 @@

    One through ten

  • Use a data frame of numbers 1 through 10 again:
  • -
    tibble(x = 1:10) %>% mutate(y = map_int(x, \(x) hotpo(x)))
    +
    tibble(x = 1:10) %>% mutate(y = map_int(x, \(x) hotpo(x)))
    @@ -3125,14 +3186,14 @@

    Until I get to 1 (if I ever do)

  • If I get to 4, 2, 1, 4, 2, 1 I’ll repeat for ever, so let’s stop when we get to 1:
  • -
    hotpo_seq <- function(x) {
    -  ans <- x
    -  while (x != 1) {
    -    x <- hotpo(x)
    -    ans <- c(ans, x)
    -  }
    -  ans
    -}
    +
    hotpo_seq <- function(x) {
    +  ans <- x
    +  while (x != 1) {
    +    x <- hotpo(x)
    +    ans <- c(ans, x)
    +  }
    +  ans
    +}
    • Strategy: keep looping “while x is not 1”.
    • @@ -3145,7 +3206,7 @@

      Trying it 1/2

    • Start at 6:
    -
    hotpo_seq(6)
    +
    hotpo_seq(6)
    [1]  6  3 10  5 16  8  4  2  1
    @@ -3157,7 +3218,7 @@

    Trying it 2/2

  • Start at 27:
  • -
    hotpo_seq(27)
    +
    hotpo_seq(27)
      [1]   27   82   41  124   62   31   94   47  142   71  214
      [12]  107  322  161  484  242  121  364  182   91  274  137
    @@ -3183,10 +3244,10 @@ 

    Which starting points have the longest sequences?

    Top 10 longest sequences

    -
    tibble(start = 1:100) %>%
    -  mutate(seq_length = map_int(
    -    start, \(start) length(hotpo_seq(start)))) %>%
    -  slice_max(seq_length, n = 10)
    +
    tibble(start = 1:100) %>%
    +  mutate(seq_length = map_int(
    +    start, \(start) length(hotpo_seq(start)))) %>%
    +  slice_max(seq_length, n = 10)
    @@ -3203,8 +3264,8 @@

    Top 10 longest sequences

    What happens if we save the entire sequence?

    -
    tibble(start = 1:7) %>%
    -  mutate(sequence = map(start, \(start) hotpo_seq(start)))
    +
    tibble(start = 1:7) %>%
    +  mutate(sequence = map(start, \(start) hotpo_seq(start)))
    @@ -3221,12 +3282,12 @@

    What happens if we save the entire sequence?

    Using the whole sequence to find its length and its max

    -
    tibble(start = 1:7) %>%
    -  mutate(sequence = map(start, \(start) hotpo_seq(start))) %>%
    -  mutate(
    -    seq_length = map_int(sequence, \(sequence) length(sequence)),
    -    seq_max = map_int(sequence, \(sequence) max(sequence))
    -  )
    +
    tibble(start = 1:7) %>%
    +  mutate(sequence = map(start, \(start) hotpo_seq(start))) %>%
    +  mutate(
    +    seq_length = map_int(sequence, \(sequence) length(sequence)),
    +    seq_max = map_int(sequence, \(sequence) max(sequence))
    +  )
    @@ -3240,11 +3301,11 @@

    Using the whole sequence to find its length and its max

    Does it work with rowwise?

    -
    tibble(start=1:7) %>% 
    -  rowwise() %>% 
    -  mutate(sequence = list(hotpo_seq(start))) %>% 
    -  mutate(seq_length = length(sequence)) %>% 
    -  mutate(seq_max = max(sequence))
    +
    tibble(start=1:7) %>% 
    +  rowwise() %>% 
    +  mutate(sequence = list(hotpo_seq(start))) %>% 
    +  mutate(seq_length = length(sequence)) %>% 
    +  mutate(seq_max = max(sequence))
    diff --git a/functions.pdf b/functions.pdf index accf55f..cd7a0dc 100644 Binary files a/functions.pdf and b/functions.pdf differ diff --git a/functions.qmd b/functions.qmd index 6678ac6..ec03c22 100644 --- a/functions.qmd +++ b/functions.qmd @@ -7,7 +7,7 @@ title: "Functions" ```{r functions-1} library(tidyverse) library(broom) -install.packages("vctrs") +# install.packages("vctrs") ``` @@ -72,6 +72,7 @@ sqrt_minus_1(3) ``` ```{r} +#| error: true q <- 17 sqrt_minus_1(q) sqrt_minus_1("text") diff --git a/graphs.pdf b/graphs.pdf index 17b1859..95c8d33 100644 Binary files a/graphs.pdf and b/graphs.pdf differ diff --git a/inference_1.pdf b/inference_1.pdf index 927bee8..6058995 100644 Binary files a/inference_1.pdf and b/inference_1.pdf differ diff --git a/inference_2.pdf b/inference_2.pdf index cab8291..925ef7f 100644 Binary files a/inference_2.pdf and b/inference_2.pdf differ diff --git a/inference_3.html b/inference_3.html index 54c0737..5f7ecc5 100644 --- a/inference_3.html +++ b/inference_3.html @@ -2592,9 +2592,9 @@

    Test and CI

    Not just coincidence. Let \(C = 100(1 - \alpha)\), so C% gives corresponding CI to level-\(\alpha\) test. Then following always true. (Symbol \(\iff\) means “if and only if”.)

    ---+++ diff --git a/inference_3.pdf b/inference_3.pdf index 8be167e..f035804 100644 Binary files a/inference_3.pdf and b/inference_3.pdf differ diff --git a/inference_4a.pdf b/inference_4a.pdf index 2ec4b73..8d323dd 100644 Binary files a/inference_4a.pdf and b/inference_4a.pdf differ diff --git a/inference_4b.pdf b/inference_4b.pdf index f7def18..ffabe89 100644 Binary files a/inference_4b.pdf and b/inference_4b.pdf differ diff --git a/inference_5a.html b/inference_5a.html index bc113b8..a2d862b 100644 --- a/inference_5a.html +++ b/inference_5a.html @@ -2512,7 +2512,7 @@

    The chi-squared test for independence

    @@ -2606,7 +2606,7 @@

    The test

  • Do chi-squared test:
  • -
    chisq.test(tab,correct=F)
    +
    chisq.test(tab, correct=F)
    
         Pearson's Chi-squared test
    diff --git a/inference_5a.pdf b/inference_5a.pdf
    index a723711..cac4231 100644
    Binary files a/inference_5a.pdf and b/inference_5a.pdf differ
    diff --git a/inference_5b.html b/inference_5b.html
    index 521999f..e9d2e88 100644
    --- a/inference_5b.html
    +++ b/inference_5b.html
    @@ -2528,12 +2528,21 @@ 

    Reading the data

    The data (some random rows)

    -
    rats %>% slice_sample(n=12)
    +
    rats %>% slice_sample(n=10)
    +
    +
    +
    rats
    +
    + +
    +
    @@ -2542,15 +2551,15 @@

    The data (some random rows)

    Boxplots

    -
    ggplot(rats, aes(y=density, x=group)) + geom_boxplot()
    +
    ggplot(rats, aes(y=density, x=group)) + geom_boxplot()

    Or, arranging groups in data (logical) order

    -
    ggplot(rats, aes(y=density, x=fct_inorder(group))) +
    -  geom_boxplot()
    +
    ggplot(rats, aes(y=density, x=fct_inorder(group))) +
    +  geom_boxplot()
    @@ -2566,8 +2575,8 @@

    Analysis of Variance

    Testing: ANOVA in R

    -
    rats.aov <- aov(density~group,data=rats)
    -summary(rats.aov)
    +
    rats.aov <- aov(density~group,data=rats)
    +summary(rats.aov)
                Df Sum Sq Mean Sq F value Pr(>F)   
     group        2   7434    3717   7.978 0.0019 **
    @@ -2590,15 +2599,15 @@ 

    Which groups are different from which?

  • First pick out each group:
  • -
    rats %>% filter(group=="Control") -> controls
    -rats %>% filter(group=="Lowjump") -> lows
    -rats %>% filter(group=="Highjump") -> highs
    +
    rats %>% filter(group=="Control") -> controls
    +rats %>% filter(group=="Lowjump") -> lows
    +rats %>% filter(group=="Highjump") -> highs

    Control vs. low

    -
    t.test(controls$density, lows$density)
    +
    t.test(controls$density, lows$density)
    
         Welch Two Sample t-test
    @@ -2618,7 +2627,7 @@ 

    Control vs. low

    Control vs. high

    -
    t.test(controls$density, highs$density)
    +
    t.test(controls$density, highs$density)
    
         Welch Two Sample t-test
    @@ -2638,7 +2647,7 @@ 

    Control vs. high

    Low vs. high

    -
    t.test(lows$density, highs$density)
    +
    t.test(lows$density, highs$density)
    
         Welch Two Sample t-test
    @@ -2692,8 +2701,8 @@ 

    Honestly Significant Differences

    Tukey on rat data

    -
    rats.aov <- aov(density~group, data = rats)
    -TukeyHSD(rats.aov)
    +
    rats.aov <- aov(density~group, data = rats)
    +TukeyHSD(rats.aov)
      Tukey multiple comparisons of means
         95% family-wise confidence level
    @@ -2727,8 +2736,8 @@ 

    Why Tukey’s procedure better than all t-tests

    Checking assumptions

    -
    ggplot(rats,aes(y = density, x = fct_inorder(group)))+
    -  geom_boxplot()
    +
    ggplot(rats,aes(y = density, x = fct_inorder(group)))+
    +  geom_boxplot()

    Assumptions:

    @@ -2740,8 +2749,8 @@

    Checking assumptions

    Normal quantile plots by group

    -
    ggplot(rats, aes(sample = density)) + stat_qq() + 
    -  stat_qq_line() + facet_wrap( ~ group)
    +
    ggplot(rats, aes(sample = density)) + stat_qq() + 
    +  stat_qq_line() + facet_wrap( ~ group)
    @@ -2772,7 +2781,7 @@

    Mood’s median test here

  • Actually do this using median_test:

  • -
    median_test(rats, density, group)
    +
    median_test(rats, density, group)
    $grand_median
     [1] 621.5
    @@ -2800,7 +2809,7 @@ 

    Comments

  • To determine which groups differ from which, can compare all possible pairs of groups via (2-sample) Mood’s median tests, then adjust P-values by multiplying by number of 2-sample Mood tests done (Bonferroni):
  • -
    pairwise_median_test(rats, density, group)
    +
    pairwise_median_test(rats, density, group)
    @@ -2822,7 +2831,7 @@

    Welch ANOVA

  • Welch ANOVA done by oneway.test as shown (for illustration):
  • -
    oneway.test(density~group, data=rats)
    +
    oneway.test(density~group, data=rats)
    
         One-way analysis of means (not assuming equal variances)
    @@ -2842,7 +2851,7 @@ 

    Games-Howell

  • Lives in package PMCMRplus. Install first.
  • -
    gamesHowellTest(density~factor(group),data=rats)
    +
    gamesHowellTest(density~factor(group),data=rats)
             Control Highjump
     Highjump 0.0056  -       
    diff --git a/inference_5b.pdf b/inference_5b.pdf
    index 3d6f0f1..8693889 100644
    Binary files a/inference_5b.pdf and b/inference_5b.pdf differ
    diff --git a/logistic.html b/logistic.html
    index ef0e9de..4d4d397 100644
    --- a/logistic.html
    +++ b/logistic.html
    @@ -2610,10 +2610,10 @@ 

    Output part 2: predicted survival probs

    On a graph

    -
    plot_cap(status.1, condition = "dose")
    +
    plot_predictions(status.1, condition = "dose")
    -
    +

    The rats, more

      @@ -2731,7 +2731,7 @@

      Predicted survival probs

    @@ -3304,7 +3304,7 @@

    Predicted probabilities 2/2

    Plot of predicted probabilities

    -
    plot_cap(model = sev.1, condition = c("Exposure", "group"),
    +
    plot_predictions(model = sev.1, condition = c("Exposure", "group"),
              type = "probs") +
       geom_point(data = prop, aes(x = Exposure, y = proportion, 
                                   colour = Severity)) -> ggg
    @@ -3316,7 +3316,7 @@

    The graph

    ggg
    -
    +

    Comments

      @@ -3540,11 +3540,11 @@

      Comments

      Making a plot

        -
      • plot_cap doesn’t quite work
      • +
      • plot_predictions doesn’t quite work
      • so don’t draw, edit, then make graph:
      -
      plot_cap(brands.1, condition = c("age", "brand", "sex"), 
      +
      plot_predictions(brands.1, condition = c("age", "brand", "sex"), 
                type = "probs", draw = FALSE)  %>% 
         ggplot(aes(x = age, y = estimate, colour = group, 
                    linetype = sex)) +
      @@ -3670,7 +3670,7 @@ 

      Trying interaction between age and gender

      Make graph again

      -
      plot_cap(brands.4, condition = c("age", "brand", "sex"), 
      +
      plot_predictions(brands.4, condition = c("age", "brand", "sex"), 
                type = "probs", draw = FALSE)  %>% 
         ggplot(aes(x = age, y = estimate, colour = group, 
                    linetype = sex)) +
      diff --git a/logistic.pdf b/logistic.pdf
      index 9acc9a5..6cb73ab 100644
      Binary files a/logistic.pdf and b/logistic.pdf differ
      diff --git a/logistic.qmd b/logistic.qmd
      index 7a0a231..98a5c7e 100644
      --- a/logistic.qmd
      +++ b/logistic.qmd
      @@ -130,7 +130,7 @@ cbind(predictions(status.1)) %>%
       ## On a graph
       
       ```{r, fig.height=4}
      -plot_cap(status.1, condition = "dose")
      +plot_predictions(status.1, condition = "dose")
       ```
       
       
      @@ -759,7 +759,7 @@ cbind(predictions(sev.1, newdata = new)) %>%
       ## Plot of predicted probabilities
       
       ```{r}
      -plot_cap(model = sev.1, condition = c("Exposure", "group"),
      +plot_predictions(model = sev.1, condition = c("Exposure", "group"),
                type = "probs") +
         geom_point(data = prop, aes(x = Exposure, y = proportion, 
                                     colour = Severity)) -> ggg
      @@ -946,11 +946,11 @@ brand 2 more.
       
       ## Making a plot
       
      -- `plot_cap` doesn't quite work
      +- `plot_predictions` doesn't quite work
       - so don't draw, edit, *then* make graph:
       
       ```{r}
      -plot_cap(brands.1, condition = c("age", "brand", "sex"), 
      +plot_predictions(brands.1, condition = c("age", "brand", "sex"), 
                type = "probs", draw = FALSE)  %>% 
         ggplot(aes(x = age, y = estimate, colour = group, 
                    linetype = sex)) +
      @@ -1074,7 +1074,7 @@ the two genders.
       ## Make graph again
       
       ```{r}
      -plot_cap(brands.4, condition = c("age", "brand", "sex"), 
      +plot_predictions(brands.4, condition = c("age", "brand", "sex"), 
                type = "probs", draw = FALSE)  %>% 
         ggplot(aes(x = age, y = estimate, colour = group, 
                    linetype = sex)) +
      diff --git a/manova.pdf b/manova.pdf
      index c830b64..1cc932b 100644
      Binary files a/manova.pdf and b/manova.pdf differ
      diff --git a/multiway.pdf b/multiway.pdf
      index 166909b..fb0d824 100644
      Binary files a/multiway.pdf and b/multiway.pdf differ
      diff --git a/numsum.pdf b/numsum.pdf
      index 9862041..c693aaa 100644
      Binary files a/numsum.pdf and b/numsum.pdf differ
      diff --git a/outline_c32.pdf b/outline_c32.pdf
      index 0fa1207..fe64ce2 100644
      Binary files a/outline_c32.pdf and b/outline_c32.pdf differ
      diff --git a/outline_c33.pdf b/outline_c33.pdf
      index e317161..78f435d 100644
      Binary files a/outline_c33.pdf and b/outline_c33.pdf differ
      diff --git a/outline_d29.pdf b/outline_d29.pdf
      index e9824f2..3ebceed 100644
      Binary files a/outline_d29.pdf and b/outline_d29.pdf differ
      diff --git a/princomp.pdf b/princomp.pdf
      index c9d2485..e8f784f 100644
      Binary files a/princomp.pdf and b/princomp.pdf differ
      diff --git a/profile.pdf b/profile.pdf
      index fd6e285..b5abee4 100644
      Binary files a/profile.pdf and b/profile.pdf differ
      diff --git a/readfile.html b/readfile.html
      index 0130897..0f93cdf 100644
      --- a/readfile.html
      +++ b/readfile.html
      @@ -2762,7 +2762,7 @@ 

      Reading from the Web; the soap data

    • Save the URL in a variable first:
    -
    url <- url("http://ritsokiguess.site/datafiles/soap.txt")
    +
    url <- "http://ritsokiguess.site/datafiles/soap.txt"
     soap <- read_delim(url, " ")
    @@ -2825,9 +2825,10 @@

    Reading it in

  • Excel spreadsheets must be “local”: cannot read one in from a URL.
  • -
    library(readxl)
    -mydata2 <- read_excel("test2.xlsx", sheet = "data")
    -mydata2
    +
    # install.packages("readxl")
    +library(readxl)
    +mydata2 <- read_excel("test2.xlsx", sheet = "data")
    +mydata2
    diff --git a/readfile.pdf b/readfile.pdf index 083efe2..53ab58c 100644 Binary files a/readfile.pdf and b/readfile.pdf differ diff --git a/regression.html b/regression.html index 5c80ad3..38f648c 100644 --- a/regression.html +++ b/regression.html @@ -2532,22 +2532,33 @@

    Check data

    Max. :586.0 Max. :14.000
    +
    +
    sleep
    +
    + +
    + +
    +
    +

    Make scatter plot of ATST (response) vs. age (explanatory) using code overleaf:

    The scatterplot

    -
    ggplot(sleep, aes(x = age, y = atst)) + geom_point()
    +
    ggplot(sleep, aes(x = age, y = atst)) + geom_point()
    -
    +

    Correlation

    • Measures how well a straight line fits the data:
    -
    with(sleep, cor(atst, age))
    +
    with(sleep, cor(atst, age))
    [1] -0.9515469
    @@ -2558,7 +2569,7 @@

    Correlation

  • Can do correlations of all pairs of variables:

  • -
    cor(sleep)
    +
    cor(sleep)
               atst        age
     atst  1.0000000 -0.9515469
    @@ -2578,22 +2589,22 @@ 

    Lowess curve

    Plot with lowess curve

    -
    ggplot(sleep, aes(x = age, y = atst)) + geom_point() +
    -  geom_smooth()
    +
    ggplot(sleep, aes(x = age, y = atst)) + geom_point() +
    +  geom_smooth()
    -
    +

    The regression

    Scatterplot shows no obvious curve, and a pretty clear downward trend. So we can run the regression:

    -
    sleep.1 <- lm(atst ~ age, data = sleep)
    +
    sleep.1 <- lm(atst ~ age, data = sleep)

    The output

    -
    summary(sleep.1)
    +
    summary(sleep.1)
    
     Call:
    @@ -2634,7 +2645,7 @@ 

    Doing things with the regression output

  • Package broom extracts info from model output in way that can be used in pipe (later):

  • -
    tidy(sleep.1)
    +
    tidy(sleep.1)
    @@ -2648,7 +2659,7 @@

    Doing things with the regression output

    also one-line summary of model:

    -
    glance(sleep.1)
    +
    glance(sleep.1)
    @@ -2662,7 +2673,7 @@

    also one-line summary of model:

    Broom part 2

    -
    sleep.1 %>% augment(sleep)
    +
    sleep.1 %>% augment(sleep)
    @@ -2689,8 +2700,8 @@

    CI for mean response and prediction intervals

    The marginaleffects package 1/2

    To get predictions for specific values, set up a dataframe with those values first:

    -
    new <- datagrid(model = sleep.1, age = c(10, 5))
    -new
    +
    new <- datagrid(model = sleep.1, age = c(10, 5))
    +new
    @@ -2706,13 +2717,13 @@

    The marginaleffects package 1/2

    The marginaleffects package 2/2

    Then feed into newdata in predictions. This contains a lot of columns, so you probably want only to display the ones you care about:

    -
    cbind(predictions(sleep.1, newdata = new)) %>% 
    -  select(estimate, conf.low, conf.high, age)
    +
    cbind(predictions(sleep.1, newdata = new)) %>% 
    +  select(estimate, conf.low, conf.high, age)
    @@ -2723,8 +2734,14 @@

    The marginaleffects package 2/2

    Prediction intervals

    These are obtained (instead) with predict as below. Use the same dataframe new as before:

    -
    pp <- predict(sleep.1, new, interval = "p")
    -cbind(new, pp) %>% select(-atst)
    +
    pp <- predict(sleep.1, new, interval = "p")
    +pp
    +
    +
           fit      lwr      upr
    +1 506.0729 475.8982 536.2475
    +2 576.2781 543.8474 608.7088
    +
    +
    cbind(new, pp) %>% select(-atst)
    @@ -2747,12 +2764,12 @@

    Comments

    That grey envelope

    Marks confidence interval for mean for all \(x\):

    -
    ggplot(sleep, aes(x = age, y = atst)) + geom_point() +
    -  geom_smooth(method = "lm") +
    -  scale_y_continuous(breaks = seq(420, 600, 20))
    +
    ggplot(sleep, aes(x = age, y = atst)) + geom_point() +
    +  geom_smooth(method = "lm") +
    +  scale_y_continuous(breaks = seq(420, 600, 20))
    -
    +

    Diagnostics

    How to tell whether a straight-line regression is appropriate?

    @@ -2765,7 +2782,7 @@

    Diagnostics

    Residual plot

    Not much pattern here — regression appropriate.

    -
    ggplot(sleep.1, aes(x = .fitted, y = .resid)) + geom_point()
    +
    ggplot(sleep.1, aes(x = .fitted, y = .resid)) + geom_point()
    @@ -2773,22 +2790,22 @@

    Residual plot

    An inappropriate regression

    Different data:

    -
    my_url <- "http://ritsokiguess.site/datafiles/curvy.txt"
    -curvy <- read_delim(my_url, " ")
    +
    my_url <- "http://ritsokiguess.site/datafiles/curvy.txt"
    +curvy <- read_delim(my_url, " ")

    Scatterplot

    -
    ggplot(curvy, aes(x = xx, y = yy)) + geom_point()
    +
    ggplot(curvy, aes(x = xx, y = yy)) + geom_point()

    Regression line, anyway

    -
    curvy.1 <- lm(yy ~ xx, data = curvy)
    -summary(curvy.1)
    +
    curvy.1 <- lm(yy ~ xx, data = curvy)
    +summary(curvy.1)
    
     Call:
    @@ -2815,7 +2832,7 @@ 

    Regression line, anyway

    Residual plot

    -
    ggplot(curvy.1, aes(x = .fitted, y = .resid)) + geom_point()
    +
    ggplot(curvy.1, aes(x = .fitted, y = .resid)) + geom_point()
    @@ -2826,20 +2843,20 @@

    No good: fixing it up

  • Fitting a curve would be better. Try this:

  • -
    curvy.2 <- lm(yy ~ xx + I(xx^2), data = curvy)
    +
    curvy.2 <- lm(yy ~ xx + I(xx^2), data = curvy)
    • Adding xx-squared term, to allow for curve.

    • Another way to do same thing: specify how model changes:

    -
    curvy.2a <- update(curvy.1, . ~ . + I(xx^2))
    +
    curvy.2a <- update(curvy.1, . ~ . + I(xx^2))

    Regression 2

    -
    tidy(curvy.2)
    +
    tidy(curvy.2)
    @@ -2848,7 +2865,7 @@

    Regression 2

    -
    glance(curvy.2) #
    +
    glance(curvy.2) #
    @@ -2871,7 +2888,7 @@

    Comments

    The residual plot now

    No problems any more:

    -
    ggplot(curvy.2, aes(x = .fitted, y = .resid)) + geom_point()
    +
    ggplot(curvy.2, aes(x = .fitted, y = .resid)) + geom_point()
    @@ -2897,9 +2914,9 @@

    Box-Cox

    Some made-up data

    -
    my_url <- "http://ritsokiguess.site/datafiles/madeup2.csv"
    -madeup <- read_csv(my_url)
    -madeup
    +
    my_url <- "http://ritsokiguess.site/datafiles/madeup2.csv"
    +madeup <- read_csv(my_url)
    +madeup
    @@ -2914,8 +2931,8 @@

    Some made-up data

    Scatterplot: faster than linear growth

    -
    ggplot(madeup, aes(x = x, y = y)) + geom_point() +
    -  geom_smooth()
    +
    ggplot(madeup, aes(x = x, y = y)) + geom_point() +
    +  geom_smooth()
    @@ -2927,7 +2944,7 @@

    Running Box-Cox

  • Output: a graph (next page):

  • -
    boxcox(y ~ x, data = madeup)
    +
    boxcox(y ~ x, data = madeup)
    @@ -2950,8 +2967,8 @@

    Did transformation straighten things?

  • Plot transformed \(y\) against \(x\). Here, log:
  • -
    ggplot(madeup, aes(x = x, y = log(y))) + geom_point() +
    -  geom_smooth()
    +
    ggplot(madeup, aes(x = x, y = log(y))) + geom_point() +
    +  geom_smooth()

    Looks much straighter.

    @@ -2959,8 +2976,8 @@

    Did transformation straighten things?

    Regression with transformed \(y\)

    -
    madeup.1 <- lm(log(y) ~ x, data = madeup)
    -glance(madeup.1)
    +
    madeup.1 <- lm(log(y) ~ x, data = madeup)
    +glance(madeup.1)
    @@ -2969,7 +2986,7 @@

    Regression with transformed \(y\)

    -
    tidy(madeup.1)
    +
    tidy(madeup.1)
    @@ -3003,15 +3020,15 @@

    Multiple regression example

    The data

    -
    my_url <- 
    -  "http://ritsokiguess.site/datafiles/regressx.txt"
    -visits <- read_delim(my_url, " ")
    +
    my_url <- 
    +  "http://ritsokiguess.site/datafiles/regressx.txt"
    +visits <- read_delim(my_url, " ")

    Check data

    -
    visits
    +
    visits
    @@ -3025,9 +3042,9 @@

    Check data

    Fit multiple regression

    -
    visits.1 <- lm(timedrs ~ phyheal + menheal + stress,
    -  data = visits)
    -summary(visits.1)
    +
    visits.1 <- lm(timedrs ~ phyheal + menheal + stress,
    +  data = visits)
    +summary(visits.1)
    
     Call:
    @@ -3063,8 +3080,8 @@ 

    The slopes

    Just menheal

    -
    visits.2 <- lm(timedrs ~ menheal, data = visits)
    -summary(visits.2)
    +
    visits.2 <- lm(timedrs ~ menheal, data = visits)
    +summary(visits.2)
    
     Call:
    @@ -3101,7 +3118,7 @@ 

    menheal by itself

    Investigating via correlation

    Leave out first column (subjno):

    -
    visits %>% select(-subjno) %>% cor()
    +
    visits %>% select(-subjno) %>% cor()
              timedrs   phyheal   menheal    stress
     timedrs 1.0000000 0.4395293 0.2555703 0.2865951
    @@ -3120,7 +3137,7 @@ 

    Investigating via correlation

    Residual plot (from timedrs on all)

    -
    ggplot(visits.1, aes(x = .fitted, y = .resid)) + geom_point()
    +
    ggplot(visits.1, aes(x = .fitted, y = .resid)) + geom_point()

    Apparently random. But…

    @@ -3128,7 +3145,7 @@

    Residual plot (from timedrs on all)

    Normal quantile plot of residuals

    -
    ggplot(visits.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()
    +
    ggplot(visits.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()

    Not normal at all; upper tail is way too long.

    @@ -3137,8 +3154,8 @@

    Normal quantile plot of residuals

    Absolute residuals

    Is there trend in size of residuals (fan-out)? Plot absolute value of residual against fitted value:

    -
    ggplot(visits.1, aes(x = .fitted, y = abs(.resid))) +
    -  geom_point() + geom_smooth()
    +
    ggplot(visits.1, aes(x = .fitted, y = abs(.resid))) +
    +  geom_point() + geom_smooth()
    @@ -3167,7 +3184,7 @@

    Box-Cox transformations

  • Extra problem: some of timedrs values are 0, but Box-Cox expects all +. Note response for boxcox:

  • -
    boxcox(timedrs + 1 ~ phyheal + menheal + stress, data = visits)
    +
    boxcox(timedrs + 1 ~ phyheal + menheal + stress, data = visits)
    @@ -3182,8 +3199,8 @@

    Comments on try 1

  • Focus on \(\lambda\) in \((-0.3,0.1)\):

  • -
    my.lambda <- seq(-0.3, 0.1, 0.01)
    -my.lambda
    +
    my.lambda <- seq(-0.3, 0.1, 0.01)
    +my.lambda
     [1] -0.30 -0.29 -0.28 -0.27 -0.26 -0.25 -0.24 -0.23 -0.22
     [10] -0.21 -0.20 -0.19 -0.18 -0.17 -0.16 -0.15 -0.14 -0.13
    @@ -3196,10 +3213,10 @@ 

    Comments on try 1

    Try 2

    -
    boxcox(timedrs + 1 ~ phyheal + menheal + stress,
    -  lambda = my.lambda,
    -  data = visits
    -)
    +
    boxcox(timedrs + 1 ~ phyheal + menheal + stress,
    +  lambda = my.lambda,
    +  data = visits
    +)
    @@ -3218,9 +3235,9 @@

    Fixing the problems

  • Then check residual plot to see that it is OK now.

  • -
    visits.3 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress,
    -  data = visits
    -)
    +
    visits.3 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress,
    +  data = visits
    +)
    • timedrs+1 because some timedrs values 0, can’t take log of 0.

    • @@ -3230,7 +3247,7 @@

      Fixing the problems

      Output

      -
      summary(visits.3)
      +
      summary(visits.3)
      
       Call:
      @@ -3268,23 +3285,23 @@ 

      Comments

      Residuals against fitted values

      -
      ggplot(visits.3, aes(x = .fitted, y = .resid)) +
      -  geom_point()
      +
      ggplot(visits.3, aes(x = .fitted, y = .resid)) +
      +  geom_point()

      Normal quantile plot of residuals

      -
      ggplot(visits.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()
      +
      ggplot(visits.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()

      Absolute residuals against fitted

      -
      ggplot(visits.3, aes(x = .fitted, y = abs(.resid))) +
      -  geom_point() + geom_smooth()
      +
      ggplot(visits.3, aes(x = .fitted, y = abs(.resid))) +
      +  geom_point() + geom_smooth()
      @@ -3307,15 +3324,15 @@

      Testing more than one \(x\) at once

    -
    visits.5 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress, 
    -               data = visits)
    -visits.6 <- lm(log(timedrs + 1) ~ stress, data = visits)
    +
    visits.5 <- lm(log(timedrs + 1) ~ phyheal + menheal + stress, 
    +               data = visits)
    +visits.6 <- lm(log(timedrs + 1) ~ stress, data = visits)

    Results of tests

    -
    anova(visits.6, visits.5)
    +
    anova(visits.6, visits.5)
    @@ -3347,8 +3364,7 @@

    The punting data

    120 130 117.58 126 140 120 140.25 129 130 140 150.17 136 -150 160 165.17 154 -
    +150 160 165.17 154

    Reading in

    @@ -3356,14 +3372,14 @@

    Reading in

  • Separated by multiple spaces with columns lined up:
  • -
    my_url <- "http://ritsokiguess.site/datafiles/punting.txt"
    -punting <- read_table(my_url)
    +
    my_url <- "http://ritsokiguess.site/datafiles/punting.txt"
    +punting <- read_table(my_url)

    The data

    -
    punting
    +
    punting
    @@ -3377,8 +3393,8 @@

    The data

    Regression and output

    -
    punting.1 <- lm(punt ~ left + right + fred, data = punting)
    -glance(punting.1)
    +
    punting.1 <- lm(punt ~ left + right + fred, data = punting)
    +glance(punting.1)
    @@ -3387,7 +3403,7 @@

    Regression and output

    -
    tidy(punting.1)
    +
    tidy(punting.1)
    @@ -3396,6 +3412,27 @@

    Regression and output

    +
    summary(punting.1)
    +
    +
    
    +Call:
    +lm(formula = punt ~ left + right + fred, data = punting)
    +
    +Residuals:
    +     Min       1Q   Median       3Q      Max 
    +-14.9325 -11.5618  -0.0315   9.0415  20.0886 
    +
    +Coefficients:
    +            Estimate Std. Error t value Pr(>|t|)
    +(Intercept)  -4.6855    29.1172  -0.161    0.876
    +left          0.2679     2.1111   0.127    0.902
    +right         1.0524     2.1477   0.490    0.636
    +fred         -0.2672     4.2266  -0.063    0.951
    +
    +Residual standard error: 14.68 on 9 degrees of freedom
    +Multiple R-squared:  0.7781,    Adjusted R-squared:  0.7042 
    +F-statistic: 10.52 on 3 and 9 DF,  p-value: 0.00267
    +
    @@ -3410,7 +3447,7 @@

    Comments

    The correlations

    -
    cor(punting)
    +
    cor(punting)
               left     right      punt      fred
     left  1.0000000 0.8957224 0.8117368 0.9722632
    @@ -3427,8 +3464,30 @@ 

    The correlations

    Just right

    -
    punting.2 <- lm(punt ~ right, data = punting)
    -anova(punting.2, punting.1)
    +
    punting.2 <- lm(punt ~ right, data = punting)
    +summary(punting.2)
    +
    +
    
    +Call:
    +lm(formula = punt ~ right, data = punting)
    +
    +Residuals:
    +     Min       1Q   Median       3Q      Max 
    +-15.7576 -11.0611   0.3656   7.8890  19.0423 
    +
    +Coefficients:
    +            Estimate Std. Error t value Pr(>|t|)    
    +(Intercept)  -3.6930    25.2649  -0.146    0.886    
    +right         1.0427     0.1692   6.162 7.09e-05 ***
    +---
    +Signif. codes:  
    +0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 13.36 on 11 degrees of freedom
    +Multiple R-squared:  0.7754,    Adjusted R-squared:  0.7549 
    +F-statistic: 37.97 on 1 and 11 DF,  p-value: 7.088e-05
    +
    +
    anova(punting.2, punting.1)
    @@ -3437,17 +3496,40 @@

    Just right

    +
    punting.3 <- lm(punt ~ left, data = punting)
    +summary(punting.3)
    +
    +
    
    +Call:
    +lm(formula = punt ~ left, data = punting)
    +
    +Residuals:
    +    Min      1Q  Median      3Q     Max 
    +-22.840 -12.298  -2.234   8.990  35.820 
    +
    +Coefficients:
    +            Estimate Std. Error t value Pr(>|t|)    
    +(Intercept)  12.8834    30.1575   0.427 0.677474    
    +left          0.9553     0.2072   4.610 0.000753 ***
    +---
    +Signif. codes:  
    +0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 16.46 on 11 degrees of freedom
    +Multiple R-squared:  0.6589,    Adjusted R-squared:  0.6279 
    +F-statistic: 21.25 on 1 and 11 DF,  p-value: 0.0007528
    +

    No significant loss by dropping other two variables.

    Comparing R-squareds

    -
    summary(punting.1)$r.squared
    +
    summary(punting.1)$r.squared
    [1] 0.7781401
    -
    summary(punting.2)$r.squared
    +
    summary(punting.2)$r.squared
    [1] 0.7753629
    @@ -3457,7 +3539,7 @@

    Comparing R-squareds

    Regression results

    -
    tidy(punting.2)
    +
    tidy(punting.2)
    @@ -3481,8 +3563,8 @@

    But

    Augmenting punting.2

    -
    punting.2 %>% augment(punting) -> punting.2.aug
    -punting.2.aug 
    +
    punting.2 %>% augment(punting) -> punting.2.aug
    +punting.2.aug 
    @@ -3496,8 +3578,8 @@

    Augmenting punting.2

    Residuals against left

    -
    ggplot(punting.2.aug, aes(x = left, y = .resid)) +
    -  geom_point()
    +
    ggplot(punting.2.aug, aes(x = left, y = .resid)) +
    +  geom_point()
    @@ -3508,15 +3590,15 @@

    Comments

  • We should add left-squared to the regression (and therefore put left back in when we do that):

  • -
    punting.3 <- lm(punt ~ left + I(left^2) + right,
    -  data = punting
    -)
    +
    punting.3 <- lm(punt ~ left + I(left^2) + right,
    +  data = punting
    +)

    Regression with left-squared

    -
    summary(punting.3)
    +
    summary(punting.3)
    
     Call:
    diff --git a/regression.pdf b/regression.pdf
    index 069d41f..99c2faf 100644
    Binary files a/regression.pdf and b/regression.pdf differ
    diff --git a/reports.pdf b/reports.pdf
    index e392c45..822d6d4 100644
    Binary files a/reports.pdf and b/reports.pdf differ
    diff --git a/running.pdf b/running.pdf
    index d9cc633..7e92e34 100644
    Binary files a/running.pdf and b/running.pdf differ
    diff --git a/survival.pdf b/survival.pdf
    index c24773b..adcd0c9 100644
    Binary files a/survival.pdf and b/survival.pdf differ
    diff --git a/tidy_extra.html b/tidy_extra.html
    index 177cd10..8c473d5 100644
    --- a/tidy_extra.html
    +++ b/tidy_extra.html
    @@ -2494,7 +2494,7 @@ 

    Packages

    The pig feed data again

    -
    my_url <- "http://ritsokiguess.site/STAC32/pigs1.txt"
    +
    my_url <- "http://ritsokiguess.site/datafiles/pigs1.txt"
     pigs <- read_table(my_url)
     pigs
    @@ -2600,16 +2600,16 @@

    Lengthen and separate

    Making longer, the better way

    -
    prevalence %>% 
    +
    prevalence  %>% 
       pivot_longer(-Species, names_to=c("disease", "location"),
    -               names_sep="_", values_to="frequency") %>% 
    -  arrange(Species, location, disease) -> prevalence_longer
    +               names_sep="_", 
    +               values_to="frequency") -> prevalence_longer 
     prevalence_longer
    @@ -2624,7 +2624,7 @@

    Making wider, different ways

    @@ -2636,7 +2636,7 @@

    Making wider, different ways

    @@ -2745,6 +2745,14 @@

    A hairy one

    +
    + +
    +
    +
    +
    diff --git a/tidy_extra.pdf b/tidy_extra.pdf index 5af979f..ed97abb 100644 Binary files a/tidy_extra.pdf and b/tidy_extra.pdf differ diff --git a/tidying.html b/tidying.html index 5b928e1..dae2600 100644 --- a/tidying.html +++ b/tidying.html @@ -2555,6 +2555,35 @@

    Making it longer

    pigs2
    +
    + +
    +
    +
    +
    +
    +

    Alternatives

    +

    Any way of choosing the columns to pivot longer is good, eg:

    +
    +
    pigs1 %>% pivot_longer(-pig, names_to="feed", 
    +                       values_to="weight") 
    +
    + +
    + +
    +
    +
    +

    or

    +
    +
    pigs1 %>% pivot_longer(starts_with("feed"), names_to="feed", 
    +                       values_to="weight") 
    +
    +
    @@ -2600,8 +2629,8 @@

    …and finally, the analysis

  • which is just what we saw before:
  • -
    weight.1 <- aov(weight ~ feed, data = pigs2)
    -summary(weight.1)
    +
    weight.1 <- aov(weight ~ feed, data = pigs2)
    +summary(weight.1)
                Df Sum Sq Mean Sq F value   Pr(>F)    
     feed         3   3521  1173.5   119.1 3.72e-11 ***
    @@ -2618,7 +2647,7 @@ 

    …and finally, the analysis

    Tukey

    -
    TukeyHSD(weight.1)
    +
    TukeyHSD(weight.1)
      Tukey multiple comparisons of means
         95% family-wise confidence level
    @@ -2641,10 +2670,10 @@ 

    Tukey

    Mean weights by feed

    To find the best and worst, get mean weight by feed group. I borrowed an idea from earlier to put the means in descending order:

    -
    pigs2 %>%
    -  group_by(feed) %>%
    -  summarize(mean_weight = mean(weight))%>%
    -  arrange(desc(mean_weight))
    +
    pigs2 %>%
    +  group_by(feed) %>%
    +  summarize(mean_weight = mean(weight))%>%
    +  arrange(desc(mean_weight))
    @@ -2659,7 +2688,7 @@

    Mean weights by feed

    Should we have any concerns about the ANOVA?

    -
    ggplot(pigs2, aes(x = feed, y = weight)) + geom_boxplot()
    +
    ggplot(pigs2, aes(x = feed, y = weight)) + geom_boxplot()
    @@ -2678,26 +2707,26 @@

    Tuberculosis

  • Some data:
  • -
    my_url <- "http://ritsokiguess.site/datafiles/tb.csv"
    -tb <- read_csv(my_url)
    +
    my_url <- "http://ritsokiguess.site/datafiles/tb.csv"
    +tb <- read_csv(my_url)

    The data (randomly chosen rows)

    -
    tb %>% slice_sample(n = 10)
    +
    tb %>% slice_sample(n = 10)

    Many rows:

    -
    nrow(tb)
    +
    nrow(tb)
    [1] 5769
    @@ -2711,9 +2740,9 @@

    What we have

  • Abbreviations here.
  • -
    tb %>% 
    -  pivot_longer(m04:fu, names_to = "genage", 
    -               values_to = "freq", values_drop_na = TRUE) -> tb2
    +
    tb %>% 
    +  pivot_longer(m04:fu, names_to = "genage", 
    +               values_to = "freq", values_drop_na = TRUE) -> tb2
    • Code for pivot_longer: @@ -2728,7 +2757,7 @@

      What we have

      Results (some)

      -
      tb2
      +
      tb2
      @@ -2752,10 +2781,10 @@

      Separating

    • For “how to split”, here “after first character”:
    -
    tb2 %>% separate_wider_position(genage, 
    -                                widths = c("gender" = 1, "age" = 4), 
    -                                too_few = "align_start") -> tb3
    -tb3
    +
    tb2 %>% separate_wider_position(genage, 
    +                                widths = c("gender" = 1, "age" = 4), 
    +                                too_few = "align_start") -> tb3
    +tb3
    @@ -2769,7 +2798,7 @@

    Separating

    Tidied tuberculosis data (some)

    -
    tb3
    +
    tb3
    @@ -2786,12 +2815,12 @@

    In practice…

  • instead of doing the pipe one step at a time, you debug it one step at a time, and when you have each step working, you use that step’s output as input to the next step, thus:
  • -
    tb %>%
    -  pivot_longer(m04:fu, names_to = "genage", 
    -               values_to = "freq", values_drop_na = TRUE) %>% 
    -  separate_wider_position(genage, 
    -                          widths = c("gender" = 1, "age" = 4), 
    -                          too_few = "align_start")
    +
    tb %>%
    +  pivot_longer(m04:fu, names_to = "genage", 
    +               values_to = "freq", values_drop_na = TRUE) %>% 
    +  separate_wider_position(genage, 
    +                          widths = c("gender" = 1, "age" = 4), 
    +                          too_few = "align_start")
    @@ -2809,9 +2838,9 @@

    In practice…

    Total tuberculosis cases by year (some of the years)

    -
    tb3 %>%
    -  filter(between(year, 1991, 1998)) %>% 
    -  group_by(year) %>% summarize(total=sum(freq))
    +
    tb3 %>%
    +  filter(between(year, 1991, 1998)) %>% 
    +  group_by(year) %>% summarize(total=sum(freq))
    @@ -2831,9 +2860,9 @@

    To find out what

  • try counting up total cases by country:
  • -
    tb3 %>% group_by(iso2) %>% 
    -  summarize(total=sum(freq)) %>% 
    -  arrange(desc(total))
    +
    tb3 %>% group_by(iso2) %>% 
    +  summarize(total=sum(freq)) %>% 
    +  arrange(desc(total))
    @@ -2848,9 +2877,9 @@

    To find out what

    What years do I have for China?

    China started recording in 1995, which is at least part of the problem:

    -
    tb3 %>% filter(iso2=="CN") %>% 
    -  group_by(year) %>% 
    -  summarize(total=sum(freq))
    +
    tb3 %>% filter(iso2=="CN") %>% 
    +  group_by(year) %>% 
    +  summarize(total=sum(freq))
    @@ -2867,9 +2896,9 @@

    First year of recording by country?

  • A lot of countries started recording in about 1995, in fact:
  • -
    tb3 %>% group_by(iso2) %>% 
    -  summarize(first_year=min(year)) %>% 
    -  count(first_year)
    +
    tb3 %>% group_by(iso2) %>% 
    +  summarize(first_year=min(year)) %>% 
    +  count(first_year)
    @@ -2886,10 +2915,10 @@

    First year of recording by country?

    Some Toronto weather data

    -
    my_url <- 
    -  "http://ritsokiguess.site/STAC32/toronto_weather.csv"
    -weather <- read_csv(my_url)
    -weather
    +
    my_url <- 
    +  "http://ritsokiguess.site/STAC32/toronto_weather.csv"
    +weather <- read_csv(my_url)
    +weather
    @@ -2916,10 +2945,10 @@

    The columns

    Off we go

    Numbers in data frame all temperatures (for different days of the month), so first step is

    -
    weather %>% 
    -  pivot_longer(d01:d31, names_to="day", 
    -               values_to="temperature", 
    -               values_drop_na = TRUE)
    +
    weather %>% 
    +  pivot_longer(d01:d31, names_to="day", 
    +               values_to="temperature", 
    +               values_drop_na = TRUE)
    @@ -2941,12 +2970,12 @@

    Element

    Handling element

    -
    weather %>%
    -  pivot_longer(d01:d31, names_to="day", 
    -               values_to="temperature", 
    -               values_drop_na = TRUE) %>% 
    -  pivot_wider(names_from=element, 
    -                values_from=temperature) 
    +
    weather %>%
    +  pivot_longer(d01:d31, names_to="day", 
    +               values_to="temperature", 
    +               values_drop_na = TRUE) %>% 
    +  pivot_wider(names_from=element, 
    +                values_from=temperature) 
    @@ -2969,12 +2998,12 @@

    Further improvements 1/2

    Further improvements 2/2

    -
    weather %>%
    -  pivot_longer(d01:d31, names_to="day", 
    -               values_to="temperature", values_drop_na = T) %>% 
    -  pivot_wider(names_from=element, values_from=temperature) %>% 
    -  mutate(Day = parse_number(day)) %>%
    -  select(-station)
    +
    weather %>%
    +  pivot_longer(d01:d31, names_to="day", 
    +               values_to="temperature", values_drop_na = T) %>% 
    +  pivot_wider(names_from=element, values_from=temperature) %>% 
    +  mutate(Day = parse_number(day)) %>%
    +  select(-station)
    @@ -2989,24 +3018,24 @@

    Further improvements 2/2

    Final step(s)

    • Make year-month-day into proper date.
    • -
    • Keep only date, tmax, tmin:
    • +
    • Keep only date, tmax, tmin:
    -
    weather %>%
    -  pivot_longer(d01:d31, names_to="day", 
    -               values_to="temperature", values_drop_na = T) %>% 
    -  pivot_wider(names_from=element, values_from=temperature) %>% 
    -  mutate(Day = parse_number(day)) %>%
    -  select(-station) %>% 
    -  unite(datestr, c(Year, Month, Day), sep = "-") %>%
    -  mutate(date = as.Date(datestr)) %>%
    -  select(c(date, tmax, tmin)) -> weather_tidy
    +
    weather %>%
    +  pivot_longer(d01:d31, names_to="day", 
    +               values_to="temperature", values_drop_na = T) %>% 
    +  pivot_wider(names_from=element, values_from=temperature) %>% 
    +  mutate(Day = parse_number(day)) %>%
    +  select(-station) %>% 
    +  unite(datestr, c(Year, Month, Day), sep = "-") %>%
    +  mutate(date = as.Date(datestr)) %>%
    +  select(c(date, tmax, tmin)) -> weather_tidy

    Our tidy data frame

    -
    weather_tidy
    +
    weather_tidy
    @@ -3023,8 +3052,8 @@

    Plotting the temperatures

  • Plot temperature against date joined by lines, but with separate lines for max and min. ggplot requires something like
  • -
    ggplot(..., aes(x = date, y = temperature)) + geom_point() + 
    -  geom_line()
    +
    ggplot(..., aes(x = date, y = temperature)) + geom_point() + 
    +  geom_line()

    only we have two temperatures, one a max and one a min, that we want to keep separate.

      @@ -3032,8 +3061,8 @@

      Plotting the temperatures

    • Then can do something like
    -
    ggplot(d, aes(x = date, y = temperature, colour = maxmin)) 
    -  + geom_point() + geom_line()
    +
    ggplot(d, aes(x = date, y = temperature, colour = maxmin)) 
    +  + geom_point() + geom_line()

    to distinguish max and min on graph.

    @@ -3045,26 +3074,26 @@

    Setting up plot

  • To make those “one column”s: pivot_longer. I save the graph to show overleaf:
  • -
    weather_tidy %>%
    -  pivot_longer(tmax:tmin, names_to="maxmin", 
    -               values_to="temperature") %>%
    -  ggplot(aes(x = date, y = temperature, colour = maxmin)) +
    -      geom_line() -> g
    +
    weather_tidy %>%
    +  pivot_longer(tmax:tmin, names_to="maxmin", 
    +               values_to="temperature") %>%
    +  ggplot(aes(x = date, y = temperature, colour = maxmin)) + geom_point() +
    +      geom_line() -> g

    The plot

    -
    g
    +
    g
    -
    +

    Summary of tidying “verbs”

    --++ diff --git a/tidying.pdf b/tidying.pdf index 5c8ca8a..1e73b33 100644 Binary files a/tidying.pdf and b/tidying.pdf differ diff --git a/tidying.qmd b/tidying.qmd index 07ceb11..afe2e07 100644 --- a/tidying.qmd +++ b/tidying.qmd @@ -439,7 +439,7 @@ weather_tidy %>% pivot_longer(tmax:tmin, names_to="maxmin", values_to="temperature") %>% ggplot(aes(x = date, y = temperature, colour = maxmin)) + geom_point() + - geom_line() + geom_line() -> g ``` ## The plot diff --git a/vector_matrix.pdf b/vector_matrix.pdf index 2c75fbd..cf115cb 100644 Binary files a/vector_matrix.pdf and b/vector_matrix.pdf differ diff --git a/wider_wrong.pdf b/wider_wrong.pdf index 237a0b1..6dea4a5 100644 Binary files a/wider_wrong.pdf and b/wider_wrong.pdf differ diff --git a/windmill.html b/windmill.html index 9ef2d55..204c5a0 100644 --- a/windmill.html +++ b/windmill.html @@ -2539,7 +2539,7 @@

    Strategy

    ggplot(windmill, aes(y = DC_output, x = wind_velocity)) +
    -  geom_point() + geom_smooth(se = F) 
    + geom_point() + geom_smooth()
    @@ -2652,6 +2652,13 @@

    Comments on residual plot

    +
    +

    normal quantile plot of residuals

    +
    +
    ggplot(DC.1, aes(sample = .resid)) + stat_qq() + stat_qq_line()
    + +
    +

    Parabolas and fitting parabola model

      @@ -2659,9 +2666,9 @@

      Parabolas and fitting parabola model

    • Fit one using lm by adding \(x^2\) to right side of model formula with +:
    -
    DC.2 <- lm(DC_output ~ wind_velocity + I(wind_velocity^2),
    -  data = windmill
    -)
    +
    DC.2 <- lm(DC_output ~ wind_velocity + I(wind_velocity^2),
    +  data = windmill
    +)
    • The I() necessary because ^ in model formula otherwise means something different (to do with interactions in ANOVA).
    • @@ -2671,18 +2678,58 @@

      Parabolas and fitting parabola model

      Parabola model output

      -
      tidy(DC.2)
      -
      +
      summary(DC.2)
      +
      +
      
      +Call:
      +lm(formula = DC_output ~ wind_velocity + I(wind_velocity^2), 
      +    data = windmill)
       
      -
      - +Residuals: + Min 1Q Median 3Q Max +-0.26347 -0.02537 0.01264 0.03908 0.19903 + +Coefficients: + Estimate Std. Error t value Pr(>|t|) +(Intercept) -1.155898 0.174650 -6.618 1.18e-06 *** +wind_velocity 0.722936 0.061425 11.769 5.77e-11 *** +I(wind_velocity^2) -0.038121 0.004797 -7.947 6.59e-08 *** +--- +Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 + +Residual standard error: 0.1227 on 22 degrees of freedom +Multiple R-squared: 0.9676, Adjusted R-squared: 0.9646 +F-statistic: 328.3 on 2 and 22 DF, p-value: < 2.2e-16
      +
      # tidy(DC.2)
      +
      +
      +
      summary(DC.2)
      +
      +
      
      +Call:
      +lm(formula = DC_output ~ wind_velocity + I(wind_velocity^2), 
      +    data = windmill)
      +
      +Residuals:
      +     Min       1Q   Median       3Q      Max 
      +-0.26347 -0.02537  0.01264  0.03908  0.19903 
      +
      +Coefficients:
      +                    Estimate Std. Error t value Pr(>|t|)    
      +(Intercept)        -1.155898   0.174650  -6.618 1.18e-06 ***
      +wind_velocity       0.722936   0.061425  11.769 5.77e-11 ***
      +I(wind_velocity^2) -0.038121   0.004797  -7.947 6.59e-08 ***
      +---
      +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
      +
      +Residual standard error: 0.1227 on 22 degrees of freedom
      +Multiple R-squared:  0.9676,    Adjusted R-squared:  0.9646 
      +F-statistic: 328.3 on 2 and 22 DF,  p-value: < 2.2e-16
      -
      glance(DC.2)
      +
      glance(DC.2)
      @@ -2706,11 +2753,19 @@

      Comments on output

      Residual plot from parabola model

      -
      ggplot(DC.2, aes(y = .resid, x = .fitted)) +
      -  geom_point()
      +
      ggplot(DC.2, aes(y = .resid, x =  .fitted)) +
      +  geom_point()
      +
      +

      normal quantile plot of residuals

      +
      +
      ggplot(DC.2, aes(sample = .resid)) + stat_qq() + stat_qq_line()
      + +
      +

      This distribution has long tails, which should worry us at least some.

      +

      Make scatterplot with fitted line and curve

        @@ -2718,9 +2773,9 @@

        Make scatterplot with fitted line and curve

      • Scatterplot with fitted line and curve like this:
      -
      ggplot(windmill, aes(y = DC_output, x = wind_velocity)) +
      -  geom_point() + geom_smooth(method = "lm", se = F) +
      -  geom_line(data = DC.2, aes(y = .fitted))
      +
      ggplot(windmill, aes(y = DC_output, x = wind_velocity)) +
      +  geom_point() + geom_smooth(method = "lm", se = F) +
      +  geom_line(data = DC.2, aes(y = .fitted))
      @@ -2769,15 +2824,36 @@

      How to fit asymptote model?

    • Make a scatterplot first to check for straightness (next page).
    -
    windmill %>% mutate(wind_pace = 1 / wind_velocity) -> windmill
    -ggplot(windmill, aes(y = DC_output, x = wind_pace)) +
    -  geom_point() + geom_smooth(se = F)
    +
    windmill %>% mutate(wind_pace = 1 / wind_velocity) -> windmill
    +ggplot(windmill, aes(y = DC_output, x = wind_pace)) +
    +  geom_point() + geom_smooth(se = F)
    • and run regression like this (output page after):
    -
    DC.3 <- lm(DC_output ~ wind_pace, data = windmill)
    +
    DC.3 <- lm(DC_output ~ wind_pace, data = windmill)
    +summary(DC.3)
    +
    +
    
    +Call:
    +lm(formula = DC_output ~ wind_pace, data = windmill)
    +
    +Residuals:
    +     Min       1Q   Median       3Q      Max 
    +-0.20547 -0.04940  0.01100  0.08352  0.12204 
    +
    +Coefficients:
    +            Estimate Std. Error t value Pr(>|t|)    
    +(Intercept)   2.9789     0.0449   66.34   <2e-16 ***
    +wind_pace    -6.9345     0.2064  -33.59   <2e-16 ***
    +---
    +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 0.09417 on 23 degrees of freedom
    +Multiple R-squared:   0.98, Adjusted R-squared:  0.9792 
    +F-statistic:  1128 on 1 and 23 DF,  p-value: < 2.2e-16
    +
    @@ -2788,7 +2864,7 @@

    Scatterplot for wind_pace

    Regression output

    -
    glance(DC.3)
    +
    glance(DC.3)
    @@ -2799,7 +2875,7 @@

    Regression output

    -
    tidy(DC.3)
    +
    tidy(DC.3)
    @@ -2822,10 +2898,18 @@

    Comments

    Residual plot for asymptote model

    -
    ggplot(DC.3, aes(y = .resid, x = .fitted)) + geom_point()
    +
    ggplot(DC.3, aes(y = .resid, x = .fitted)) + geom_point()
    +
    +

    normal quantile plot of residuals

    +
    +
    ggplot(DC.3, aes(sample = .resid)) + stat_qq() + stat_qq_line()
    + +
    +

    This is skewed (left), but is not bad (and definitely better than the one for the parabola model).

    +

    What’s in w2

    -
    w2
    +
    w2
    @@ -2865,12 +2949,12 @@

    Making the plot

  • pivot_longer, then plot:
  • -
    w2 %>%
    -  pivot_longer(linear:asymptote, names_to="model", 
    -               values_to="fit") %>%
    -  ggplot(aes(x = wind_velocity, y = DC_output)) +
    -  geom_point() +
    -  geom_line(aes(y = fit, colour = model)) 
    +
    w2 %>%
    +  pivot_longer(linear:asymptote, names_to="model", 
    +               values_to="fit") %>%
    +  ggplot(aes(x = wind_velocity, y = DC_output)) +
    +  geom_point() +
    +  geom_line(aes(y = fit, colour = model)) 
    @@ -2888,7 +2972,7 @@

    Comments

    Asymptote model summary

    -
    tidy(DC.3)
    +
    tidy(DC.3)
    @@ -2939,8 +3023,8 @@

    Job done, kinda

  • Extend range of wind.velocity to 1 to 16 (steps of 0.5), and predict DC.output according to the two models:
  • -
    wv <- seq(1, 16, 0.5)
    -wv
    +
    wv <- seq(1, 16, 0.5)
    +wv
     [1]  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0
     [14]  7.5  8.0  8.5  9.0  9.5 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5
    @@ -2960,13 +3044,13 @@ 

    Setting up data frame to predict from

  • So create data frame called wv_new with those in:
  • -
    wv_new <- tibble(wind_velocity = wv, wind_pace = 1 / wv)
    +
    wv_new <- tibble(wind_velocity = wv, wind_pace = 1 / wv)

    wv_new

    -
    wv_new
    +
    wv_new
    @@ -2983,24 +3067,24 @@

    Doing predictions, one for each model

  • Use same names as before:
  • -
    linear <- predict(DC.1, wv_new)
    -parabola <- predict(DC.2, wv_new)
    -asymptote <- predict(DC.3, wv_new)
    +
    linear <- predict(DC.1, wv_new)
    +parabola <- predict(DC.2, wv_new)
    +asymptote <- predict(DC.3, wv_new)
    • Put it all into a data frame for plotting, along with original data:
    -
    my_fits <- tibble(
    -  wind_velocity = wv_new$wind_velocity,
    -  linear, parabola, asymptote
    -)
    +
    my_fits <- tibble(
    +  wind_velocity = wv_new$wind_velocity,
    +  linear, parabola, asymptote
    +)

    my_fits

    -
    my_fits
    +
    my_fits
    @@ -3017,16 +3101,16 @@

    Making a plot 1/2

  • To make a plot, we use the same trick as last time to get all three predictions on a plot with a legend (saving result to add to later):
  • -
    my_fits %>%
    -    pivot_longer(
    -    linear:asymptote,
    -    names_to="model", 
    -    values_to="fit"
    -  ) %>%
    -  ggplot(aes(
    -    y = fit, x = wind_velocity,
    -    colour = model
    -  )) + geom_line() -> g
    +
    my_fits %>%
    +    pivot_longer(
    +    linear:asymptote,
    +    names_to="model", 
    +    values_to="fit"
    +  ) %>%
    +  ggplot(aes(
    +    y = fit, x = wind_velocity,
    +    colour = model
    +  )) + geom_line() -> g
    @@ -3035,7 +3119,7 @@

    Making a plot 2/2

  • The observed wind velocities were in this range:
  • -
    (vels <- range(windmill$wind_velocity))
    +
    (vels <- range(windmill$wind_velocity))
    [1]  2.45 10.20
    @@ -3044,10 +3128,10 @@

    Making a plot 2/2

  • DC.output between 0 and 3 from asymptote model. Add rectangle to graph around where the data were:
  • -
    g + geom_rect(
    -  xmin = vels[1], xmax = vels[2], ymin = 0, ymax = 3,
    -  alpha=0, colour = "black"
    -)
    +
    g + geom_rect(
    +  xmin = vels[1], xmax = vels[2], ymin = 0, ymax = 3,
    +  alpha=0, colour = "black"
    +)
    @@ -3069,7 +3153,7 @@

    Comments (2)

  • For parabola model:
  • -
    tidy(DC.2)
    +
    tidy(DC.2)
    @@ -3086,7 +3170,7 @@

    Comments (2)

    Comments (3): asymptote model

    -
    tidy(DC.3)
    +
    tidy(DC.3)
    diff --git a/windmill.pdf b/windmill.pdf index e7e3b00..c96c980 100644 Binary files a/windmill.pdf and b/windmill.pdf differ diff --git a/with_categ.html b/with_categ.html index c5db980..6788114 100644 --- a/with_categ.html +++ b/with_categ.html @@ -2549,7 +2549,30 @@

    Running through aov and lm

    and now lm

    pigs.2 <- lm(weight ~ feed, data = pigs)
    -tidy(pigs.2)
    +summary(pigs.2)
    +
    +
    
    +Call:
    +lm(formula = weight ~ feed, data = pigs)
    +
    +Residuals:
    +   Min     1Q Median     3Q    Max 
    +-3.900 -2.025 -0.570  1.845  5.000 
    +
    +Coefficients:
    +            Estimate Std. Error t value Pr(>|t|)    
    +(Intercept)   60.620      1.404  43.190  < 2e-16 ***
    +feedfeed2      8.680      1.985   4.373 0.000473 ***
    +feedfeed3     33.480      1.985  16.867 1.30e-11 ***
    +feedfeed4     25.620      1.985  12.907 7.11e-10 ***
    +---
    +Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
    +
    +Residual standard error: 3.138 on 16 degrees of freedom
    +Multiple R-squared:  0.9572,    Adjusted R-squared:  0.9491 
    +F-statistic: 119.1 on 3 and 16 DF,  p-value: 3.72e-11
    +
    +
    tidy(pigs.2)
    @@ -2558,7 +2581,7 @@

    and now lm

    -
    glance(pigs.2)
    +
    glance(pigs.2)
    @@ -2587,7 +2610,7 @@

    Reproducing the ANOVA

  • Pass the fitted model object into anova:
  • -
    anova(pigs.2)
    +
    anova(pigs.2)
    @@ -2602,7 +2625,7 @@

    Reproducing the ANOVA

  • But no Tukey this way:
  • -
    TukeyHSD(pigs.2)
    +
    TukeyHSD(pigs.2)
    Error in UseMethod("TukeyHSD"): no applicable method for 'TukeyHSD' applied to an object of class "lm"
    @@ -2621,14 +2644,14 @@

    The crickets

    The crickets data

    Read the data:

    -
    my_url <- "http://ritsokiguess.site/datafiles/crickets2.csv"
    -crickets <- read_csv(my_url)
    -crickets %>% sample_n(10)
    +
    my_url <- "http://ritsokiguess.site/datafiles/crickets2.csv"
    +crickets <- read_csv(my_url)
    +crickets %>% slice_sample(n = 10)
    @@ -2637,12 +2660,12 @@

    The crickets data

    Fit model with lm

    -
    crickets.1 <- lm(pulse_rate ~ temperature + species, 
    -                 data = crickets)
    +
    crickets.1 <- lm(pulse_rate ~ temperature + species, 
    +                 data = crickets)

    Can I remove anything? No:

    -
    drop1(crickets.1, test = "F") 
    +
    drop1(crickets.1, test = "F") 
    @@ -2657,7 +2680,7 @@

    Fit model with lm

    The summary

    -
    summary(crickets.1)
    +
    summary(crickets.1)
    
     Call:
    @@ -2696,18 +2719,18 @@ 

    To end with a graph

  • This graph seems to need a title, which I define first.
  • -
    t1 <- "Pulse rate against temperature for two species of crickets"
    -t2 <- "Temperature in degrees Celsius"
    -ggplot(crickets, aes(x = temperature, y = pulse_rate,
    -  colour = species)) +
    -  geom_point() + geom_smooth(method = "lm", se = FALSE) +
    -  ggtitle(t1, t2) -> g
    +
    t1 <- "Pulse rate against temperature for two species of crickets"
    +t2 <- "Temperature in degrees Celsius"
    +ggplot(crickets, aes(x = temperature, y = pulse_rate,
    +  colour = species)) +
    +  geom_point() + geom_smooth(method = "lm", se = FALSE) +
    +  ggtitle(t1, t2) -> g

    The graph

    -
    g
    +
    g