
Extract ordinal cutpoints from a fitted gllvmTMB model
Source:R/extract-cutpoints.R
extract_cutpoints.RdFor traits fitted with ordinal_probit() or ordinal_logit(), returns
a tidy data frame with the K - 2 estimated cutpoints
\(\tau_2, \ldots, \tau_{K-1}\) per trait, with optional standard
errors from the joint sdreport.
Arguments
- fit
A fit returned by
gllvmTMB()with at least oneordinal_probit()orordinal_logit()trait.- quiet
Suppress the note emitted when
tau_secannot be filled in because the fit has nosd_report. Internal callers that display only the cutpoint estimates (print(),tidy()) passTRUE: a note explaining an absent standard-error column is noise when no such column is shown.
Value
A data frame with columns
traitTrait label (factor level from
data[[trait]]).cutpoint_indexInteger index \(k \in \{2, \ldots, K-1\}\).
cutpoint_labelCharacter label
"cutpoint_<k>".tau_estimateEstimated \(\tau_k\) on the trait's own latent scale (probit for an
ordinal_probit()trait, logit for anordinal_logit()trait).tau_seStandard error from the joint sdreport, or
NAif the report is unavailable.
If the fit contains no ordinal_probit() or ordinal_logit()
traits, returns a zero-row data frame with the same columns.
Details
Convention: gllvmTMB follows Hadfield (2015) — \(\tau_1 = 0\) is
fixed for identifiability and the K - 2 free cutpoints are reported
as cutpoint_2, cutpoint_3, etc. This differs from brms, which
reports K - 1 cutpoints as Intercept[1..K-1].
This extractor includes Wald standard errors, so it refuses fits made with non-unit likelihood weights. The fitted cutpoint parameters remain point estimates, but weighted-objective uncertainty is not calibrated.
References
Hadfield, J. D. (2015). Increasing the efficiency of MCMC for hierarchical phylogenetic models of categorical traits using reduced mixed models. Methods Ecol. Evol. 6:706-714. doi:10.1111/2041-210X.12354
See also
ordinal_probit() for the family constructor and the
threshold-trait theory reference list.
Examples
## Three ordinal traits scored on 5 plants at each of 30 sites: vigour
## (3 categories), leaf damage (4) and flowering (3). Each score comes
## from a hidden continuous "liability" -- the trait mean, plus a site
## effect, plus noise with standard deviation 1 -- cut at thresholds
## (stored in cuts, starting at tau_1 = 0): score 1 if the liability is
## below tau_1, score 2 if it lies between tau_1 and tau_2, and so on.
set.seed(1)
cuts <- list(vigour = c(0, 0.8), damage = c(0, 0.6, 1.4),
flower = c(0, 1))
mu <- c(vigour = 0.3, damage = 0.2, flower = -0.2)
n_site <- 30
dat <- expand.grid(plant = 1:5, site = seq_len(n_site),
trait = names(cuts))
site_eff <- matrix(rnorm(n_site * 3, sd = 0.8), n_site, 3)
liability <- mu[as.character(dat$trait)] +
site_eff[cbind(dat$site, as.integer(dat$trait))] + rnorm(nrow(dat))
dat$value <- NA_integer_
for (tr in names(cuts)) {
rows <- dat$trait == tr
dat$value[rows] <- findInterval(liability[rows], cuts[[tr]]) + 1L
}
dat$site <- factor(dat$site)
## Ordinal responses are coded 1, 2, ..., K. indep() gives each trait
## its own between-site variance; the 5 plants per site make it
## estimable.
fit <- gllvmTMB(
value ~ 0 + trait + indep(0 + trait | site),
data = dat,
trait = "trait",
unit = "site",
family = ordinal_probit()
)
## The cutpoints are the thresholds on the liability (probit) scale.
## The first threshold is fixed at tau_1 = 0 to identify the model, so
## a trait with K categories has K - 2 estimated cutpoints: one each
## for vigour and flowering, two for damage. tau_se is a large-sample
## (Wald) standard error.
cp <- extract_cutpoints(fit)
## Add the thresholds used to simulate the data, for comparison
cp$tau_true <- c(cuts$vigour[2], cuts$damage[2:3], cuts$flower[2])
cp
#> trait cutpoint_index cutpoint_label tau_estimate tau_se tau_true
#> 1 vigour 2 cutpoint_2 0.7857366 0.12111462 0.8
#> 2 damage 2 cutpoint_2 0.5256471 0.09667978 0.6
#> 3 damage 3 cutpoint_3 1.3259814 0.15039542 1.4
#> 4 flower 2 cutpoint_2 0.8211681 0.12714513 1.0
## Standard errors are optional. A fit made with se = FALSE skips them
## and gives the same cutpoints, with tau_se = NA. extract_cutpoints()
## then prints a note saying so; quiet = TRUE turns the note off.
fit_no_se <- gllvmTMB(
value ~ 0 + trait + indep(0 + trait | site),
data = dat,
trait = "trait",
unit = "site",
family = ordinal_probit(),
control = gllvmTMBcontrol(se = FALSE)
)
extract_cutpoints(fit_no_se, quiet = TRUE)
#> trait cutpoint_index cutpoint_label tau_estimate tau_se
#> 1 vigour 2 cutpoint_2 0.7857366 NA
#> 2 damage 2 cutpoint_2 0.5256471 NA
#> 3 damage 3 cutpoint_3 1.3259814 NA
#> 4 flower 2 cutpoint_2 0.8211681 NA