Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: modelbased
Title: Estimation of Model-Based Predictions, Contrasts and Means
Version: 0.17.0.7
Version: 0.17.0.8
Authors@R:
c(person(given = "Dominique",
family = "Makowski",
Expand Down
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
meaningful predictions, slopes, and interaction contrasts using modelbased
post-estimation functions.

* `estimate_means()` with `backend = "emmeans"` now supports ordinal models,
e.g. from `MASS::polr()` or `ordinal::clm()`. `predict` accepts the ordinal
modes of *emmeans* (`"prob"`, `"cum.prob"`, `"exc.prob"`,
`"linear.predictor"`, `"latent"` and `"mean.class"`). For `"prob"`, the
output has one row per response category, in a `Response` column. For
`"cum.prob"`, `"exc.prob"` and `"linear.predictor"`, the output has one row
per threshold, in a `Threshold` column. For ordinal models, the default is
now `predict = "prob"`, which returns the same probabilities as the
`"marginaleffects"` backend.

## Bug fixes

* Fixed issue in `estimate_contrasts()` with wrong assignment of estimates in
Expand Down
6 changes: 6 additions & 0 deletions R/estimate_means.R
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
#' `"unlink"`, or `"log"`. If `predict = NULL` (default), the most appropriate
#' transformation is selected (which usually is `"response"`). See also
#' [this vignette](https://CRAN.R-project.org/package=emmeans/vignettes/transformations.html).
#' For ordinal models (e.g. from `MASS::polr()` or `ordinal::clm()`),
#' `estimate_means()` also accepts the ordinal modes of *emmeans*:
#' `"prob"`, `"cum.prob"`, `"exc.prob"`, `"linear.predictor"`, `"latent"`
#' and `"mean.class"` (see `vignette("models", package = "emmeans")`). For
#' these models, the default is `"prob"`, which returns one probability per
#' response category.
#'
#' See also section _Predictions on different scales_.
#'
Expand Down
99 changes: 98 additions & 1 deletion R/get_emmeans.R
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ get_emmeans <- function(
# setup arguments
fun_args <- list(model, specs = my_args$emmeans_specs, at = my_args$emmeans_at)

# ordinal models: prediction mode, used for column names when formatting
ordinal_mode <- NULL

# handle distributional parameters
if (inherits(model, "brmsfit")) {
if (identical(predict, "response")) {
Expand All @@ -77,7 +80,31 @@ get_emmeans <- function(
}
} else {
dpars <- FALSE
fun_args$type <- predict
# for ordinal models, emmeans ignores `type = "response"`, and we need the
# `mode` argument instead. Probabilities are the default.
ordinal_model <- .is_emmeans_ordinal(model)
if (ordinal_model && identical(predict, "response")) {
predict <- "prob"
}
if (predict %in% .emmeans_ordinal_types) {
fun_args$mode <- predict
# some modes add a pseudo-factor to the reference grid (the response
# categories, or the thresholds). We add it to `specs`, else emmeans
# averages over it
if (ordinal_model) {
ordinal_mode <- predict
pseudo_factor <- .emmeans_ordinal_pseudo_factor(model, predict)
if (!is.null(pseudo_factor)) {
if (inherits(fun_args$specs, "formula")) {
fun_args$specs <- pseudo_factor
} else {
fun_args$specs <- c(fun_args$specs, pseudo_factor)
}
}
}
} else {
fun_args$type <- predict
}
}

# add dots
Expand Down Expand Up @@ -113,6 +140,7 @@ get_emmeans <- function(
attr(estimated, "focal_terms") <- my_args$emmeans_specs
attr(estimated, "transform") <- TRUE
attr(estimated, "keep_iterations") <- keep_iterations
attr(estimated, "ordinal_mode") <- ordinal_mode

estimated
}
Expand All @@ -122,6 +150,54 @@ get_emmeans <- function(
# HELPERS (guess arguments) -----------------------------------------------
# =========================================================================

.emmeans_ordinal_types <- c(
"latent",
"linear.predictor",
"cum.prob",
"exc.prob",
"prob",
"mean.class"
)


# column names for the estimates of each ordinal mode
.emmeans_ordinal_estimate_names <- c(
latent = "Latent",
linear.predictor = "Linear_predictor",
cum.prob = "Probability",
exc.prob = "Probability",
prob = "Probability",
mean.class = "Mean_class"
)


# frequentist ordinal models, which support the `mode` argument in emmeans
.is_emmeans_ordinal <- function(model) {
if (inherits(model, "brmsfit")) {
return(FALSE)
}
m_info <- insight::model_info(model, response = 1, verbose = FALSE)
isTRUE(m_info$is_ordinal) && !isTRUE(m_info$is_bayesian)
}


# name of the pseudo-factor that emmeans adds to the reference grid for
# ordinal models: the response categories for `mode = "prob"`, and the
# thresholds for `mode = "cum.prob"`, `"exc.prob"` and `"linear.predictor"`.
# emmeans names the response pseudo-factor after the left-hand side of the
# formula, e.g. `factor(y)`, so we need the response term, not the variable
.emmeans_ordinal_pseudo_factor <- function(model, mode) {
switch(
mode,
prob = insight::find_terms(model, verbose = FALSE)$response,
cum.prob = ,
exc.prob = ,
linear.predictor = "cut",
NULL
)
}


#' @keywords internal
.guess_emmeans_arguments <- function(model, by = NULL, verbose = TRUE, ...) {
# Gather info
Expand Down Expand Up @@ -194,6 +270,7 @@ get_emmeans <- function(
} else {
means <- as.data.frame(stats::confint(x, level = ci))
means$df <- NULL
means <- .clean_names_emmeans_ordinal(means, x, model)
means <- .clean_names_frequentist(means, predict, m_info)
}

Expand All @@ -212,6 +289,26 @@ get_emmeans <- function(
}


# for ordinal models, renames the estimate column and the pseudo-factor
# columns (response categories or thresholds)
.clean_names_emmeans_ordinal <- function(means, x, model) {
ordinal_mode <- attributes(x)$ordinal_mode
if (is.null(ordinal_mode)) {
return(means)
}
estimate_name <- x@misc$estName
if (!is.null(estimate_name) && estimate_name %in% colnames(means)) {
colnames(means)[colnames(means) == estimate_name] <- .emmeans_ordinal_estimate_names[[ordinal_mode]]
Comment thread
strengejacke marked this conversation as resolved.
}
pseudo_factor <- .emmeans_ordinal_pseudo_factor(model, ordinal_mode)
if (!is.null(pseudo_factor) && pseudo_factor %in% colnames(means)) {
new_name <- if (ordinal_mode == "prob") "Response" else "Threshold"
colnames(means)[colnames(means) == pseudo_factor] <- new_name
}
means
}


# adds posterior draws to output for emmeans objects
.add_posterior_draws_emmeans <- function(info, estimated) {
# add posterior draws?
Expand Down
5 changes: 4 additions & 1 deletion R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@
"Median",
"MAP",
"Coefficient",
"Odds_ratio"
"Odds_ratio",
"Latent",
"Linear_predictor",
"Mean_class"
)
dpars <- insight::find_auxiliary(model, verbose = FALSE)
if (!is.null(dpars)) {
Expand Down
6 changes: 6 additions & 0 deletions man/estimate_contrasts.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions man/estimate_means.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions man/estimate_slopes.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions man/get_emmeans.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading