library(car) # for regression diagnostics
library(broom) # for tidy output
library(ggfortify) # for model diagnostics
library(knitr) # for kable
library(ggeffects)
library(emmeans) # for estimating marginal means
library(MASS) # for glm.nb
library(tidyverse) # for data wrangling
library(brms)
library(tidybayes)
library(broom.mixed)
library(rstan)
library(cmdstanr)
library(patchwork)
library(DHARMa)
library(easystats)
library(modelsummary)
source("helperFunctions.R")Bayesian GLMM Part7
1 Preparations
Load the necessary libraries
2 Scenario
In an honours thesis from (1992), Mullens was investigating the ways that cane toads ( Bufo marinus ) respond to conditions of hypoxia. Toads show two different kinds of breathing patterns, lung or buccal, requiring them to be treated separately in the experiment. Her aim was to expose toads to a range of O2 concentrations, and record their breathing patterns, including parameters such as the expired volume for individual breaths. It was desirable to have around 8 replicates to compare the responses of the two breathing types, and the complication is that animals are expensive, and different individuals are likely to have different O2 profiles (leading to possibly reduced power). There are two main design options for this experiment;
- One animal per O2 treatment, 8 concentrations, 2 breathing types. With 8 replicates the experiment would require 128 animals, but that this could be analysed as a completely randomized design
- One O2 profile per animal, so that each animal would be used 8 times and only 16 animals are required (8 lung and 8 buccal breathers)
Mullens decided to use the second option so as to reduce the number of animals required (on financial and ethical grounds). By selecting this option, she did not have a set of independent measurements for each oxygen concentration, by repeated measurements on each animal across the 8 oxygen concentrations.
| BREATH | TOAD | O2LEVEL | FREQBUC | SFREQBUC |
|---|---|---|---|---|
| lung | a | 0 | 10.6 | 3.256 |
| lung | a | 5 | 18.8 | 4.336 |
| lung | a | 10 | 17.4 | 4.171 |
| lung | a | 15 | 16.6 | 4.074 |
| ... | ... | ... | ... | ... |
| BREATH | Categorical listing of the breathing type treatment (buccal = buccal breathing toads, lung = lung breathing toads). This is the between subjects (plots) effect and applies to the whole toads (since a single toad can only be one breathing type - either lung or buccal). Equivalent to Factor A (between plots effect) in a split-plot design |
| TOAD | These are the subjects (equivalent to the plots in a split-plot design: Factor B). The letters in this variable represent the labels given to each individual toad. |
| O2LEVEL | 0 through to 50 represent the the different oxygen concentrations (0% to 50%). The different oxygen concentrations are equivalent to the within plot effects in a split-plot (Factor C). |
| FREQBUC | The frequency of buccal breathing - the response variable |
| SFREQBUC | Square root transformed frequency of buccal breathing - the response variable |
3 Read in the data
Rows: 168 Columns: 5
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): BREATH, TOAD
dbl (3): O2LEVEL, FREQBUC, SFREQBUC
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Rows: 168
Columns: 5
$ BREATH <chr> "lung", "lung", "lung", "lung", "lung", "lung", "lung", "lung…
$ TOAD <chr> "a", "a", "a", "a", "a", "a", "a", "a", "b", "b", "b", "b", "…
$ O2LEVEL <dbl> 0, 5, 10, 15, 20, 30, 40, 50, 0, 5, 10, 15, 20, 30, 40, 50, 0…
$ FREQBUC <dbl> 10.6, 18.8, 17.4, 16.6, 9.4, 11.4, 2.8, 4.4, 21.6, 17.4, 22.4…
$ SFREQBUC <dbl> 3.255764, 4.335897, 4.171331, 4.074310, 3.065942, 3.376389, 1…
spc_tbl_ [168 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
$ BREATH : chr [1:168] "lung" "lung" "lung" "lung" ...
$ TOAD : chr [1:168] "a" "a" "a" "a" ...
$ O2LEVEL : num [1:168] 0 5 10 15 20 30 40 50 0 5 ...
$ FREQBUC : num [1:168] 10.6 18.8 17.4 16.6 9.4 11.4 2.8 4.4 21.6 17.4 ...
$ SFREQBUC: num [1:168] 3.26 4.34 4.17 4.07 3.07 ...
- attr(*, "spec")=
.. cols(
.. BREATH = col_character(),
.. TOAD = col_character(),
.. O2LEVEL = col_double(),
.. FREQBUC = col_double(),
.. SFREQBUC = col_double()
.. )
- attr(*, "problems")=<externalptr>
mullens (168 rows and 5 variables, 5 shown)
ID | Name | Type | Missings | Values | N
---+----------+-----------+----------+---------+------------
1 | BREATH | character | 0 (0.0%) | buccal | 104 (61.9%)
| | | | lung | 64 (38.1%)
---+----------+-----------+----------+---------+------------
2 | TOAD | character | 0 (0.0%) | a | 8 ( 4.8%)
| | | | b | 8 ( 4.8%)
| | | | c | 8 ( 4.8%)
| | | | d | 8 ( 4.8%)
| | | | e | 8 ( 4.8%)
| | | | f | 8 ( 4.8%)
| | | | g | 8 ( 4.8%)
| | | | h | 8 ( 4.8%)
| | | | i | 8 ( 4.8%)
| | | | j | 8 ( 4.8%)
| | | | (...) |
---+----------+-----------+----------+---------+------------
3 | O2LEVEL | numeric | 0 (0.0%) | [0, 50] | 168
---+----------+-----------+----------+---------+------------
4 | FREQBUC | numeric | 0 (0.0%) | [0, 49] | 168
---+----------+-----------+----------+---------+------------
5 | SFREQBUC | numeric | 0 (0.0%) | [0, 7] | 168
------------------------------------------------------------
| Unique | Missing Pct. | Mean | SD | Min | Median | Max | Histogram | |
|---|---|---|---|---|---|---|---|---|
| O2LEVEL | 8 | 0 | 21.2 | 16.4 | 0.0 | 17.5 | 50.0 | |
| FREQBUC | 90 | 0 | 13.7 | 10.6 | 0.0 | 10.1 | 49.0 | |
| SFREQBUC | 90 | 0 | 3.4 | 1.5 | 0.0 | 3.2 | 7.0 | |
| N | % | |||||||
| BREATH | buccal | 104 | 61.9 | |||||
| lung | 64 | 38.1 | ||||||
| TOAD | a | 8 | 4.8 | |||||
| b | 8 | 4.8 | ||||||
| c | 8 | 4.8 | ||||||
| d | 8 | 4.8 | ||||||
| e | 8 | 4.8 | ||||||
| f | 8 | 4.8 | ||||||
| g | 8 | 4.8 | ||||||
| h | 8 | 4.8 | ||||||
| i | 8 | 4.8 | ||||||
| j | 8 | 4.8 | ||||||
| k | 8 | 4.8 | ||||||
| l | 8 | 4.8 | ||||||
| m | 8 | 4.8 | ||||||
| n | 8 | 4.8 | ||||||
| o | 8 | 4.8 | ||||||
| p | 8 | 4.8 | ||||||
| q | 8 | 4.8 | ||||||
| r | 8 | 4.8 | ||||||
| s | 8 | 4.8 | ||||||
| t | 8 | 4.8 | ||||||
| u | 8 | 4.8 |
| BREATH | O2LEVEL | Unique | Missing Pct. | Mean | SD | Min | Median | Max | Histogram | |
|---|---|---|---|---|---|---|---|---|---|---|
| FREQBUC | lung | 0.0 | 6 | 0 | 4.3 | 5.2 | 0.0 | 2.7 | 13.8 | |
| lung | 5.0 | 6 | 0 | 8.2 | 7.7 | 0.0 | 5.3 | 18.8 | ||
| lung | 10.0 | 8 | 0 | 13.2 | 7.7 | 0.0 | 16.7 | 22.4 | ||
| lung | 15.0 | 8 | 0 | 11.4 | 8.2 | 0.0 | 10.6 | 27.0 | ||
| lung | 20.0 | 8 | 0 | 11.9 | 8.3 | 4.4 | 9.6 | 31.0 | ||
| lung | 30.0 | 8 | 0 | 10.2 | 6.7 | 3.8 | 8.7 | 25.0 | ||
| lung | 40.0 | 7 | 0 | 11.1 | 15.5 | 2.8 | 6.7 | 49.0 | ||
| lung | 50.0 | 8 | 0 | 7.2 | 5.8 | 2.8 | 5.3 | 21.0 | ||
| buccal | 0.0 | 12 | 0 | 25.7 | 11.4 | 8.4 | 28.0 | 45.8 | ||
| buccal | 5.0 | 12 | 0 | 23.6 | 11.2 | 7.6 | 21.4 | 44.0 | ||
| buccal | 10.0 | 12 | 0 | 20.2 | 9.0 | 9.6 | 16.2 | 38.0 | ||
| buccal | 15.0 | 13 | 0 | 17.9 | 10.4 | 5.2 | 16.8 | 40.2 | ||
| buccal | 20.0 | 12 | 0 | 12.7 | 8.1 | 3.0 | 9.4 | 29.2 | ||
| buccal | 30.0 | 13 | 0 | 13.9 | 11.1 | 2.8 | 9.6 | 39.0 | ||
| buccal | 40.0 | 12 | 0 | 8.1 | 4.9 | 0.8 | 6.4 | 16.8 | ||
| buccal | 50.0 | 12 | 0 | 6.9 | 5.6 | 2.4 | 5.2 | 22.2 | ||
| SFREQBUC | lung | 0.0 | 6 | 0 | 1.5 | 1.5 | 0.0 | 1.6 | 3.7 | |
| lung | 5.0 | 6 | 0 | 2.4 | 1.7 | 0.0 | 2.3 | 4.3 | ||
| lung | 10.0 | 8 | 0 | 3.3 | 1.6 | 0.0 | 4.1 | 4.7 | ||
| lung | 15.0 | 8 | 0 | 3.1 | 1.5 | 0.0 | 3.2 | 5.2 | ||
| lung | 20.0 | 8 | 0 | 3.3 | 1.0 | 2.1 | 3.1 | 5.6 | ||
| lung | 30.0 | 8 | 0 | 3.1 | 1.0 | 1.9 | 2.9 | 5.0 | ||
| lung | 40.0 | 7 | 0 | 2.9 | 1.7 | 1.7 | 2.6 | 7.0 | ||
| lung | 50.0 | 8 | 0 | 2.6 | 0.9 | 1.7 | 2.3 | 4.6 | ||
| buccal | 0.0 | 12 | 0 | 4.9 | 1.2 | 2.9 | 5.3 | 6.8 | ||
| buccal | 5.0 | 12 | 0 | 4.7 | 1.2 | 2.8 | 4.6 | 6.6 | ||
| buccal | 10.0 | 12 | 0 | 4.4 | 1.0 | 3.1 | 4.0 | 6.2 | ||
| buccal | 15.0 | 13 | 0 | 4.1 | 1.2 | 2.3 | 4.1 | 6.3 | ||
| buccal | 20.0 | 12 | 0 | 3.4 | 1.1 | 1.7 | 3.1 | 5.4 | ||
| buccal | 30.0 | 13 | 0 | 3.5 | 1.4 | 1.7 | 3.1 | 6.2 | ||
| buccal | 40.0 | 12 | 0 | 2.7 | 0.9 | 0.9 | 2.5 | 4.1 | ||
| buccal | 50.0 | 12 | 0 | 2.5 | 0.9 | 1.5 | 2.3 | 4.7 | ||
| N | % | |||||||||
| BREATH | buccal | 104 | 61.9 | |||||||
| lung | 64 | 38.1 | ||||||||
| TOAD | a | 8 | 4.8 | |||||||
| b | 8 | 4.8 | ||||||||
| c | 8 | 4.8 | ||||||||
| d | 8 | 4.8 | ||||||||
| e | 8 | 4.8 | ||||||||
| f | 8 | 4.8 | ||||||||
| g | 8 | 4.8 | ||||||||
| h | 8 | 4.8 | ||||||||
| i | 8 | 4.8 | ||||||||
| j | 8 | 4.8 | ||||||||
| k | 8 | 4.8 | ||||||||
| l | 8 | 4.8 | ||||||||
| m | 8 | 4.8 | ||||||||
| n | 8 | 4.8 | ||||||||
| o | 8 | 4.8 | ||||||||
| p | 8 | 4.8 | ||||||||
| q | 8 | 4.8 | ||||||||
| r | 8 | 4.8 | ||||||||
| s | 8 | 4.8 | ||||||||
| t | 8 | 4.8 | ||||||||
| u | 8 | 4.8 |
4 Exploratory data analysis
Model formula: \[ \begin{align} y_i \sim{} \mathcal{Pois}(\lambda_i)\\ ln(\lambda_i) =\boldsymbol{\beta} \bf{X_i} + \boldsymbol{\gamma} \bf{Z_i} \end{align} \]
where \(\boldsymbol{\beta}\) and \(\boldsymbol{\gamma}\) are vectors of the fixed and random effects parameters respectively and \(\bf{X}\) is the model matrix representing the overall intercept and effects of copper, distance and their interaction on the number of number of worms. Area of the place segment was also incorporated as an offset. \(\bf{Z}\) represents a cell means model matrix for the random intercepts associated with individual plates.
In this example, the individual TOADs are the random blocks. Each toad can only be of one BREATH type (they are either predominantly buccal breathers or predominantly lung breathers). Hence, BREATH is a between TOAD (block) effect. The frequency of buccal breathing of ach TOAD was measured under eight different oxygen levels and thus, these represent the within block effect, as will the interaction between breathing type and oxygen level.
The response in this case is a little tricky since it is a proportion, but without the full original measurements. For example a frequency of 50% would indicate that half of the breaths taken by the toad during the monitoring phase were buccal breaths. Ideally, it would be good to have the actual counts (both the number of buccal breaths and the total number of breaths). That way, we could model the data against a binomial distribution.
As it is, we only have the proportion (as a percentage). Although we could model this against a beta distribution, this could be complicated if there are proportions of either 0 or 1 (100).
To help guide us through this and the other typical model assumptions, lets start by graphing the frequency of buccal breathing against breathing type and oxygen level. However, before we do, we need to make sure that all categorical variables are declared as factors (including the random effect). If we intend to model against a beta distribution, we will need a version of the response that is represented on a scale between 0 and 1 (but not include 0 or 1).
So starting with the raw response.
Conclusions:
- there is a very clear relationship between mean and variance (boxplots that are higher up the y-axis are taller).
- it might be possible to address this via a logarithmic or root transformation, however this is the least favourable option (particularly root transformations due to issues with back-transformations).
If we intend to use a beta distribution, we could repeat the above with the scaled response. Since the rescaling maintains the same ranking and relative spacing, the plot will look the same as above, just with a different y-axis. However, our interpretation will change. Under a beta distribution (with logit link), we expect that the variance and mean will be related. We expect that the distributions will become more varied and more symmetrical as the expected mean shifts towards 0.5. Distributions approaching 0 and 1 will be more asymmetrical and smaller. This indeed does seem to be the case here.
Now lets explore the oxygen trends (separately for each breathing type).
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Conclusions:
- although linearity seems reasonable for the buccal breathers, there is definitely evidence of non linearity in the lung breathers.
- it might be interesting to fit polynomial trends for oxygen concentration.
ggplot(mullens, aes(y = pzBUC, x = O2LEVEL, color = BREATH)) +
geom_smooth() +
geom_point() +
facet_wrap(~ BREATH + TOAD, scales = "free")`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Conclusions:
- it does appear that different toads (within the same breathing type) have very different levels of buccal breathing
- the individual toads also have varying responses to changes in oxygen concentration
- it might be useful to explore random intercept/slope models.
5 Fit the model
Explore the default priors
mullens.form <- bf(pBUC ~ BREATH * poly(O2LEVEL, 3) + (1 | TOAD),
family = zero_one_inflated_beta()
)
get_prior(mullens.form, data = mullens) prior class coef group resp dpar nlpar
(flat) b
(flat) b BREATHlung
(flat) b BREATHlung:polyO2LEVEL31
(flat) b BREATHlung:polyO2LEVEL32
(flat) b BREATHlung:polyO2LEVEL33
(flat) b polyO2LEVEL31
(flat) b polyO2LEVEL32
(flat) b polyO2LEVEL33
beta(1, 1) coi
student_t(3, 0, 2.5) Intercept
gamma(0.01, 0.01) phi
student_t(3, 0, 2.5) sd
student_t(3, 0, 2.5) sd TOAD
student_t(3, 0, 2.5) sd Intercept TOAD
beta(1, 1) zoi
lb ub source
default
(vectorized)
(vectorized)
(vectorized)
(vectorized)
(vectorized)
(vectorized)
(vectorized)
0 1 default
default
0 default
0 default
0 (vectorized)
0 (vectorized)
0 1 default
Start by fitting with priors only Remember that when fitting polynomials, they are essentially centered…
# A tibble: 2 × 3
BREATH `logit(median(pBUC))` `logit(mad(pBUC))`
<fct> <dbl> <dbl>
1 buccal -1.87 -2.05
2 lung -2.48 -2.79
Warning in logit(mullens$pBUC): proportions remapped to (0.025, 0.975)
[1] -1.983737
Warning in logit(mullens$pBUC): proportions remapped to (0.025, 0.975)
[1] 0.8044649
Warning in logit(mullens$pBUC): proportions remapped to (0.025, 0.975)
(Intercept) BREATHlung
Inf 1.641316
poly(O2LEVEL, 3)1 poly(O2LEVEL, 3)2
10.331042 10.331042
poly(O2LEVEL, 3)3 BREATHlung:poly(O2LEVEL, 3)1
10.331042 16.738202
BREATHlung:poly(O2LEVEL, 3)2 BREATHlung:poly(O2LEVEL, 3)3
16.738202 16.738202
priors <- prior(normal(-2, 2), class = "Intercept") +
prior(normal(0, 1), class = "b") +
## prior(normal(0,1), class='b', coef = "BREATHlung") +
# prior(gamma(2,1), class='sd') +
prior(student_t(3, 0, 2), class = "sd") +
prior(gamma(0.01, 0.01), class = "phi") +
## prior(gamma(2, 1), class='phi') +
prior(beta(1, 1), class = "zoi") +
prior(beta(1, 1), class = "coi")
mullens.form <- bf(pBUC ~ BREATH * poly(O2LEVEL, 3) + (1 | TOAD),
family = zero_one_inflated_beta()
)
mullens.brm <- brm(mullens.form,
data = mullens,
prior = priors,
sample_prior = "only",
iter = 5000,
warmup = 2500,
thin = 5,
chains = 3, cores = 3,
refresh = 0,
seed = 123,
control = list(adapt_delta = 0.99, max_treedepth = 20),
backend = "rstan"
)Compiling Stan program...
Start sampling
Warning: There were 31 divergent transitions after warmup. See
https://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup
to find out why this is a problem and how to eliminate them.
Warning: Examine the pairs() plot to diagnose sampling problems
The desired updates require recompiling the model
Compiling Stan program...
Start sampling
priors <- prior(normal(-2, 2), class = "Intercept") +
prior(normal(0, 2), class = "b") +
prior(student_t(3, 0, 2), class = "sd") +
prior(gamma(0.01, 0.01), class = "phi") +
prior(beta(1, 1), class = "zoi") +
prior(beta(1, 1), class = "coi")
mullens.brm3 <- update(mullens.brm2,
prior = priors, refresh = 0,
cores = 3, seed = 123
)The desired updates require recompiling the model
Compiling Stan program...
Start sampling
You are calculating adjusted predictions on the population-level (i.e.
`type = "fixed"`) for a *generalized* linear mixed model.
This may produce biased estimates due to Jensen's inequality. Consider
setting `bias_correction = TRUE` to correct for this bias.
See also the documentation of the `bias_correction` argument.
Note: uncertainty of error terms are not taken into account. Consider
setting `interval` to "prediction". This will call `posterior_predict()`
instead of `posterior_epred()`.
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Data points may overlap. Use the `jitter` argument to add some amount of
random variation to the location of data points and avoid overplotting.
MCMC sampling diagnostics ::: {.panel-tabset} ## brms :::: {.panel-tabset} ### stan plots
:::: :::
6 Model validation
mullens.resids <- make_brms_dharma_res(mullens.brm3, integerResponse = FALSE)
wrap_elements(~ testUniformity(mullens.resids)) +
wrap_elements(~ plotResiduals(mullens.resids, form = factor(rep(1, nrow(mullens))))) +
wrap_elements(~ plotResiduals(mullens.resids, quantreg = TRUE)) +
wrap_elements(~ testDispersion(mullens.resids))7 Partial effects plot
7.1 ggpredict
Note: uncertainty of error terms are not taken into account. Consider
setting `interval` to "prediction". This will call `posterior_predict()`
instead of `posterior_epred()`.
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Data points may overlap. Use the `jitter` argument to add some amount of
random variation to the location of data points and avoid overplotting.
7.2 ggemmeans
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Warning in glmmTMB::glmmTMB(formula = cond_formula, family = family, data =
data): some components missing from 'family': downstream methods may fail
Data points may overlap. Use the `jitter` argument to add some amount of
random variation to the location of data points and avoid overplotting.
newdata <- with(mullens, list(
O2LEVEL = seq(min(O2LEVEL), max(O2LEVEL), len = 100),
BREATH = levels(BREATH)
))
mullens.em <-
mullens.brm3 |>
emmeans(~ O2LEVEL | BREATH, at = newdata, type = "response") |>
as.data.frame()Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
mullens.em |>
ggplot(aes(y = response, x = O2LEVEL, colour = BREATH, fill = BREATH)) +
geom_ribbon(aes(ymin = lower.HPD, ymax = upper.HPD),
alpha = 0.2, colour = NA
) +
geom_line()8 Model investigation
Family: zero_one_inflated_beta
Links: mu = logit; phi = identity; zoi = identity; coi = identity
Formula: pBUC ~ BREATH * poly(O2LEVEL, 3) + (1 | TOAD)
Data: mullens (Number of observations: 168)
Draws: 3 chains, each with iter = 5000; warmup = 2500; thin = 5;
total post-warmup draws = 1500
Multilevel Hyperparameters:
~TOAD (Number of levels: 21)
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
sd(Intercept) 0.54 0.11 0.38 0.78 1.00 1226 1349
Regression Coefficients:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
Intercept -1.77 0.16 -2.09 -1.46 1.00 1121
BREATHlung -0.38 0.25 -0.86 0.12 1.00 1303
polyO2LEVEL31 -4.34 0.50 -5.29 -3.33 1.00 1549
polyO2LEVEL32 0.25 0.48 -0.68 1.16 1.00 1438
polyO2LEVEL33 0.10 0.47 -0.80 1.01 1.00 1543
BREATHlung:polyO2LEVEL31 1.62 0.77 0.13 3.04 1.00 1401
BREATHlung:polyO2LEVEL32 -1.28 0.74 -2.72 0.15 1.00 1511
BREATHlung:polyO2LEVEL33 0.42 0.72 -0.95 1.84 1.00 1435
Tail_ESS
Intercept 1141
BREATHlung 1457
polyO2LEVEL31 1496
polyO2LEVEL32 1460
polyO2LEVEL33 1413
BREATHlung:polyO2LEVEL31 1486
BREATHlung:polyO2LEVEL32 1383
BREATHlung:polyO2LEVEL33 1497
Further Distributional Parameters:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
phi 34.18 4.44 25.97 43.57 1.00 1466 1333
zoi 0.05 0.02 0.02 0.08 1.00 1530 1313
coi 0.11 0.10 0.00 0.35 1.00 1407 1314
Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).
mullens.brm3 |>
as_draws_df() |>
mutate(across(everything(), exp)) |>
dplyr::select(matches("^b_.*|^sd_.*")) |>
summarise_draws(
median,
HDInterval::hdi,
rhat,
length,
ess_bulk, ess_tail,
Pl = ~ mean(.x < 1),
Pg = ~ mean(.x > 1)
) |>
knitr::kable()Warning: Dropping 'draws_df' class as required metadata was removed.
| variable | median | lower | upper | rhat | length | ess_bulk | ess_tail | Pl | Pg |
|---|---|---|---|---|---|---|---|---|---|
| b_Intercept | 0.1670042 | 0.1189784 | 0.2250192 | 1.0018252 | 1500 | 962.8235 | 1102.214 | 1.0000000 | 0.0000000 |
| b_BREATHlung | 0.6836448 | 0.3720328 | 1.0847048 | 1.0003004 | 1500 | 1051.6154 | 1110.226 | 0.9366667 | 0.0633333 |
| b_polyO2LEVEL31 | 0.0031985 | 0.0007305 | 0.0084637 | 0.9997699 | 1500 | 1290.7153 | 1530.815 | 1.0000000 | 0.0000000 |
| b_polyO2LEVEL32 | 1.5942031 | 0.3869634 | 3.8228459 | 0.9999437 | 1500 | 1503.3559 | 1280.052 | 0.1880000 | 0.8120000 |
| b_polyO2LEVEL33 | 1.0841982 | 0.2266756 | 2.5565466 | 1.0004433 | 1500 | 1462.1332 | 1307.588 | 0.4380000 | 0.5620000 |
| b_BREATHlung:polyO2LEVEL31 | 46.1674803 | 0.9903850 | 226.9142129 | 0.9996964 | 1500 | 1528.7125 | 1340.766 | 0.0006667 | 0.9993333 |
| b_BREATHlung:polyO2LEVEL32 | 0.1092211 | 0.0071674 | 0.5038358 | 1.0070014 | 1500 | 1677.8320 | 1403.666 | 0.9953333 | 0.0046667 |
| b_BREATHlung:polyO2LEVEL33 | 2.1747581 | 0.1256471 | 11.6626643 | 1.0002384 | 1500 | 1515.7266 | 1524.102 | 0.2246667 | 0.7753333 |
| sd_TOAD__Intercept | 1.7069957 | 1.4309808 | 2.1585563 | 1.0001341 | 1500 | 948.3748 | 1270.547 | 0.0000000 | 1.0000000 |
[1] 21.25
[1] 0.1401548
mullens.brm3 |>
as_draws_df() |>
dplyr::select(matches("^b_.*")) |>
exp() |>
summarise_draws(
median,
HDInterval::hdi,
rhat,
length,
ess_bulk, ess_tail,
Pl = ~ mean(.x < 1),
Pg = ~ mean(.x > 1)
)Warning: Dropping 'draws_df' class as required metadata was removed.
# A tibble: 8 × 10
variable median lower upper rhat length ess_bulk ess_tail Pl
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 b_Intercept 1.67e-1 1.19e-1 2.25e-1 1.00 1500 963. 1102. 1 e+0
2 b_BREATHlung 6.84e-1 3.72e-1 1.08e+0 1.00 1500 1052. 1110. 9.37e-1
3 b_polyO2LEVEL31 3.20e-3 7.30e-4 8.46e-3 1.000 1500 1291. 1531. 1 e+0
4 b_polyO2LEVEL32 1.59e+0 3.87e-1 3.82e+0 1.000 1500 1503. 1280. 1.88e-1
5 b_polyO2LEVEL33 1.08e+0 2.27e-1 2.56e+0 1.00 1500 1462. 1308. 4.38e-1
6 b_BREATHlung:p… 4.62e+1 9.90e-1 2.27e+2 1.000 1500 1529. 1341. 6.67e-4
7 b_BREATHlung:p… 1.09e-1 7.17e-3 5.04e-1 1.01 1500 1678. 1404. 9.95e-1
8 b_BREATHlung:p… 2.17e+0 1.26e-1 1.17e+1 1.00 1500 1516. 1524. 2.25e-1
# ℹ 1 more variable: Pg <dbl>
# Bayesian R2 with Compatibility Interval
Conditional R2: 0.553 (95% CI [0.452, 0.632])
Marginal R2: 0.154 (95% CI [0.062, 0.287])
R2_Bayes R2_Bayes.lower R2_Bayes.upper R2_Bayes_marginal
1 0.5530387 0.4617761 0.6421551 0.1535782
R2_Bayes_marginal.lower R2_Bayes_marginal.upper .width .point .interval
1 0.06248566 0.2872945 0.95 median hdci
9 Further investigations
Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
degree = linear:
BREATH O2LEVEL.trend lower.HPD upper.HPD
buccal -2.91e-02 -4.13e-02 -1.61e-02
lung -1.41e-02 -3.50e-02 4.50e-03
degree = quadratic:
BREATH O2LEVEL.trend lower.HPD upper.HPD
buccal 1.44e-04 -2.94e-04 5.07e-04
lung -7.83e-04 -1.48e-03 -1.06e-04
degree = cubic:
BREATH O2LEVEL.trend lower.HPD upper.HPD
buccal 1.99e-06 -2.58e-05 2.51e-05
lung 2.08e-05 -2.26e-05 6.09e-05
Point estimate displayed: median
HPD interval probability: 0.95
mullens.brm3 |>
emtrends(specs = "BREATH", var = "O2LEVEL", max.degree = 3) |>
gather_emmeans_draws() |>
summarise(median_hdci(.value),
Pl = mean(.value < 0),
Pg = mean(.value > 0)
)Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
`summarise()` has grouped output by 'BREATH'. You can override using the
`.groups` argument.
# A tibble: 6 × 10
# Groups: BREATH [2]
BREATH degree y ymin ymax .width .point .interval Pl Pg
<fct> <fct> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
1 buccal linear -2.91e-2 -4.13e-2 -1.61e-2 0.95 median hdci 1 0
2 buccal quadra… 1.44e-4 -2.87e-4 5.17e-4 0.95 median hdci 0.254 0.746
3 buccal cubic 1.99e-6 -2.20e-5 2.89e-5 0.95 median hdci 0.438 0.562
4 lung linear -1.41e-2 -3.54e-2 4.15e-3 0.95 median hdci 0.913 0.0867
5 lung quadra… -7.83e-4 -1.50e-3 -1.18e-4 0.95 median hdci 0.985 0.0147
6 lung cubic 2.08e-5 -2.26e-5 6.09e-5 0.95 median hdci 0.167 0.833
mullens.brm3 |>
emmeans(~ BREATH | O2LEVEL, at = list(O2LEVEL = c(0, 21, 50))) |>
pairs() |>
gather_emmeans_draws() |>
mutate(.value = exp(.value)) |>
summarise(median_hdci(.value),
Pl = mean(.value < 1),
Pg = mean(.value > 1)
)Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
`summarise()` has grouped output by 'contrast'. You can override using the
`.groups` argument.
# A tibble: 3 × 10
# Groups: contrast [1]
contrast O2LEVEL y ymin ymax .width .point .interval Pl Pg
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
1 buccal - lung 0 2.97 1.36 5.19 0.95 median hdci 0.000667 0.999
2 buccal - lung 21 1.19 0.604 1.92 0.95 median hdci 0.257 0.743
3 buccal - lung 50 1.06 0.522 1.91 0.95 median hdci 0.424 0.576
mullens.grid <- with(
mullens,
list(
O2LEVEL = seq(min(O2LEVEL), max(O2LEVEL), len = 1000),
BREATH = levels(BREATH)
)
)
## mullens.grid <- with(mullens,
## list(O2LEVEL = modelr::seq_range(O2LEVEL, n = 1000),
## BREATH = levels(BREATH)))
## At what point does the evidence for the curves being different stop
mullens.brm3 |>
emmeans(~ BREATH | O2LEVEL, at = mullens.grid) |>
pairs() |>
gather_emmeans_draws() |>
mutate(.value = exp(.value)) |>
summarise(median_hdci(.value),
Pl = mean(.value < 1),
Pg = mean(.value > 1)
) |>
filter(Pg < 0.90) |>
slice(1:3)Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
`summarise()` has grouped output by 'contrast'. You can override using the
`.groups` argument.
# A tibble: 3 × 10
# Groups: contrast [1]
contrast O2LEVEL y ymin ymax .width .point .interval Pl Pg
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
1 buccal - lung 13.6 1.43 0.757 2.31 0.95 median hdci 0.101 0.899
2 buccal - lung 13.6 1.42 0.756 2.30 0.95 median hdci 0.101 0.899
3 buccal - lung 13.7 1.42 0.764 2.31 0.95 median hdci 0.103 0.897
## Find the peak
newdata <- mullens.brm3 |>
emmeans(~ O2LEVEL | BREATH, at = mullens.grid) |>
as.data.frame()Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
# A tibble: 2 × 2
BREATH value
<fct> <dbl>
1 buccal 0
2 lung 14.1
## With uncertainty
mullens.brm3 |>
emmeans(~ O2LEVEL | BREATH, at = mullens.grid) |>
gather_emmeans_draws() |>
group_by(.draw, BREATH) |>
summarise(value = O2LEVEL[which.max(.value)]) |>
ungroup() |>
group_by(BREATH) |>
summarise(median_hdci(value))Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
`summarise()` has grouped output by '.draw'. You can override using the
`.groups` argument.
# A tibble: 2 × 7
BREATH y ymin ymax .width .point .interval
<fct> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
1 buccal 0 0 0 0.95 median hdci
2 lung 14.1 0 22.8 0.95 median hdci
# Or via Derivatives
## mullens.brm3 |> estimate_slopes(trend = "O2LEVEL",
## at = c("BREATH='lung'","O2LEVEL"))
## mullens.brm2 |>
## emtrends(~O2LEVEL|BREATH, var = "O2LEVEL",
## cov.red = \(x) seq(min(x), max(x), length = 10)
## ) |>
## summary() |>
## ggplot(aes(y = O2LEVEL.trend, x = O2LEVEL, colour = BREATH)) +
## geom_hline(yintercept = 0, linetype = "dashed") +
## geom_ribbon(aes(ymin = lower.HPD, ymax = upper.HPD, fill = BREATH), alpha = 0.2) +
## geom_line()10 Summary figure
mullens.grid <- with(
mullens,
list(
BREATH = levels(BREATH),
O2LEVEL = modelr::seq_range(O2LEVEL, n = 100)
)
)
newdata <- mullens.brm2 |>
emmeans(~ O2LEVEL | BREATH, at = mullens.grid, type = "response") |>
as.data.frame()Warning: Method 'parse_bf' is deprecated. Please use 'brmsterms' instead.
O2LEVEL BREATH response lower.HPD upper.HPD
0.0000000 buccal 0.2107378 0.1527691 0.2672369
0.5050505 buccal 0.2091359 0.1548155 0.2668167
1.0101010 buccal 0.2074718 0.1509585 0.2608619
1.5151515 buccal 0.2055807 0.1562472 0.2643178
2.0202020 buccal 0.2037584 0.1542856 0.2605384
2.5252525 buccal 0.2019476 0.1535954 0.2582075
Point estimate displayed: median
Results are back-transformed from the logit scale
HPD interval probability: 0.95
ggplot() +
geom_ribbon(
data = newdata,
aes(
ymin = lower.HPD, ymax = upper.HPD,
x = O2LEVEL, fill = BREATH
), alpha = 0.3
) +
geom_line(
data = newdata,
aes(y = response, x = O2LEVEL, color = BREATH)
) +
scale_y_continuous("Buccal breathing rate", labels = function(x) 100 * x) +
theme_classic()## prior_summary(mullens.brm)
## pars <- mullens.brm |> get_variables()
## wch <- grepl('^b_.*|^sd_.*|phi', pars, perl=TRUE)
## g <- vector('list', length=sum(wch)-1)
## names(g) <- pars[wch][-1]
## for (i in pars[wch]) {
## print(i)
## if (i == 'b_Intercept') next
## p <- mullens.brm |> hypothesis(paste0(i,'=0'), class='') |> plot()
## g[[i]] <- p[[1]]
## }
## patchwork::wrap_plots(g)
stan_trace(mullens.brm$fit, pars = pars[wch])
stan_ac(mullens.brm$fit, pars = pars[wch])
stan_rhat(mullens.brm$fit, pars = pars[wch])
stan_rhat(mullens.brm$fit)
stan_ess(mullens.brm$fit)
preds <- posterior_predict(mullens.brm, nsamples = 250, summary = FALSE)
mullens.resids <- createDHARMa(
simulatedResponse = t(preds),
observedResponse = mullens$pBUC,
fittedPredictedResponse = apply(preds, 2, median),
integerResponse = FALSE
)
plot(mullens.resids)
testDispersion(mullens.resids)