Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
65a0642
Wording of table headers
strengejacke Nov 12, 2026
0a54d85
Change footer line
strengejacke Nov 12, 2026
af3ba19
minor fix
strengejacke Nov 12, 2026
c8216b1
fix test
strengejacke Nov 12, 2026
b656c45
update snaps
strengejacke Nov 12, 2026
4a4dfb0
fix test
strengejacke Nov 12, 2026
59d1714
snaps
strengejacke Nov 12, 2026
a33fc1e
snaps
strengejacke Nov 12, 2026
98dfaae
update snaps
strengejacke Nov 12, 2026
b4d6193
fix
strengejacke Nov 12, 2026
1b5e8ef
snaps
strengejacke Nov 12, 2026
911e213
snap
strengejacke Nov 12, 2026
4cdf668
remove new
strengejacke Nov 12, 2026
185d168
Merge branch 'main' into strengejacke/issue651
strengejacke Nov 12, 2026
b19afd2
Merge branch 'main' into strengejacke/issue651
strengejacke Nov 12, 2026
058ffd6
Merge branch 'main' into strengejacke/issue651
strengejacke Nov 12, 2026
3795c8e
snaps
strengejacke Nov 12, 2026
0f79c11
update test
strengejacke Nov 12, 2026
0d366f9
snap
strengejacke Nov 12, 2026
5ad9ce4
fix test
strengejacke Nov 12, 2026
fd67cc8
fix test
strengejacke Nov 12, 2026
5bd487c
add test
strengejacke Nov 12, 2026
97c31cb
news
strengejacke Nov 12, 2026
04a1dbd
news
strengejacke Nov 12, 2026
01d4b10
Merge branch 'main' into strengejacke/issue651
strengejacke Nov 12, 2026
5089874
check eval value
strengejacke Nov 12, 2026
043e108
Fix spelling check failures
Copilot Nov 12, 2026
828e51b
missing space
strengejacke Nov 12, 2026
a939dfe
add callout
strengejacke Nov 12, 2026
27d086a
plots only on win
strengejacke Nov 12, 2026
3981084
Add win variants
strengejacke Nov 12, 2026
49a57cb
remove duplicates
strengejacke Nov 12, 2026
ae953ab
address comments
strengejacke Nov 12, 2026
58df795
handle mixed models
strengejacke Nov 12, 2026
219fbf5
handle mixed models
strengejacke Nov 12, 2026
d423e88
fix
strengejacke Nov 12, 2026
7fbf6c4
add test
strengejacke Nov 12, 2026
8e92e13
fix
strengejacke Nov 12, 2026
4ae38d5
minor
strengejacke Nov 12, 2026
5bd449a
footer for emmeans
strengejacke Nov 12, 2026
6e2a1ad
refactor a bit
strengejacke Nov 12, 2026
07ae2c2
update
strengejacke Nov 12, 2026
aea72e2
update
strengejacke Nov 12, 2026
acd6fa7
fix
strengejacke Nov 12, 2026
52f70e2
news
strengejacke Nov 12, 2026
3ffad66
update test
strengejacke Nov 12, 2026
cac5e35
snap
strengejacke Nov 12, 2026
15fee6d
snap
strengejacke Nov 12, 2026
29898c4
format
strengejacke Nov 12, 2026
ac8c8c2
snap
strengejacke Nov 12, 2026
b8128b3
snaps
strengejacke Nov 12, 2026
f2f0def
snaps
strengejacke Nov 12, 2026
c60c2ca
fix
strengejacke Nov 12, 2026
0ad9c4c
skip
strengejacke Nov 12, 2026
33412b1
fix tests
strengejacke Nov 12, 2026
069a108
spellcheck
strengejacke Nov 12, 2026
459eece
structure code
strengejacke Nov 12, 2026
cb4862d
update snap
strengejacke Nov 13, 2026
368f967
snap
strengejacke Nov 13, 2026
d2cc5f4
win-snap
strengejacke Nov 13, 2026
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.18.0.5
Version: 0.18.0.6
Authors@R:
c(person(given = "Dominique",
family = "Makowski",
Expand Down
15 changes: 15 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# modelbased (devel)

## Breaking Changes

* Table headers and footers are now harmonized to conceptually align with the
package documentation (see the `estimate` argument in `?estimate_means`, as
well as the vignettes on the modelisation-approach
(https://easystats.github.io/modelbased/articles/post_estimation_framework.html)
and technical details on marginalization
(https://easystats.github.io/modelbased/articles/technical_marginalization.html)).
Specifically, `estimate` defines the *unit of interest* for your *estimand*,
distinguishing between predictions *conditioned* on specific, theoretical
individual levels and *marginal* predictions averaged over empirical
observations at the sample or population level. Furthermore, table footers for
mixed models are now more informative, detailing how random effects are
handled.

## Changes

* `effectsize = "emmeans"` in `estimate_contrasts()` now also works for `polr`
Expand Down
2 changes: 1 addition & 1 deletion R/data.R
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ NULL


#' @docType data
#' @title Sample dataset from patients with somatic symptons
#' @title Sample dataset from patients with somatic symptoms
#' @name stigma
#' @keywords data
#'
Expand Down
10 changes: 5 additions & 5 deletions R/estimate_contrasts.R
Original file line number Diff line number Diff line change
Expand Up @@ -600,15 +600,15 @@ estimate_contrasts.default <- function(
suffix <- "Inequality Analysis"
type <- "inequality"
} else {
suffix <- "Contrasts Analysis"
suffix <- "Contrasts"
}
attr(out, "table_title") <- c(
switch(
estimate,
specific = paste("Model-based", suffix),
typical = paste("Marginal", suffix),
average = paste("Averaged", suffix),
population = paste("Counterfactual", suffix, "(G-computation)")
specific = ,
typical = paste("Conditional", suffix),
average = paste("Marginal", suffix),
population = paste("Marginal", suffix, "(G-computation)")
),
"blue"
)
Expand Down
8 changes: 4 additions & 4 deletions R/estimate_means.R
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,10 @@ estimate_means <- function(
attr(means, "table_title") <- c(
switch(
estimate,
specific = "Model-based Predictions",
typical = "Estimated Marginal Means",
average = "Average Predictions",
population = "Average Counterfactual Predictions"
specific = ,
typical = "Conditional Predictions",
average = "Marginal Predictions",
population = "Marginal Predictions (G-computation)"
),
"blue"
)
Expand Down
2 changes: 1 addition & 1 deletion R/estimate_predicted.R
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ estimate_relation <- function(
attr(out, "datagrid") <- data
attr(out, "focal_terms") <- grid_specs$at_specs$varname
attr(out, "preserve_range") <- grid_specs$preserve_range
attr(out, "table_title") <- c("Model-based Predictions", "blue")
attr(out, "table_title") <- c("Conditional Predictions", "blue")
attr(out, "coef_name") <- "Predicted"
attr(out, "model_info") <- insight::model_info(model, response = 1)
attr(out, "predict") <- predict
Expand Down
11 changes: 10 additions & 1 deletion R/get_emcontrasts.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ get_emcontrasts <- function(
fun_args$mode <- ordinal$mode
}
.check_ordinal_response_filter(my_args$response_filter, ordinal$mode)
.check_ordinal_response_filter(my_args$response_contrast_filter, ordinal$mode, "contrast")
.check_ordinal_response_filter(
my_args$response_contrast_filter,
ordinal$mode,
"contrast"
)
}

# add dots
Expand Down Expand Up @@ -199,6 +203,11 @@ get_emcontrasts <- function(
keep_iterations <- FALSE
}

# emmeans is always conditioning on a typical cluster
if (insight::is_mixed_model(model)) {
attr(estimated, "re_form_na") <- TRUE
}

attr(out, "contrast") <- my_args$contrast
attr(out, "predict") <- predict
attr(out, "at") <- my_args$by
Expand Down
15 changes: 13 additions & 2 deletions R/get_emmeans.R
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ get_emmeans <- function(
keep_iterations <- FALSE
}

# emmeans is always conditioning on a typical cluster
if (insight::is_mixed_model(model)) {
attr(estimated, "re_form_na") <- TRUE
}

attr(estimated, "at") <- my_args$by
attr(estimated, "by") <- my_args$by
attr(estimated, "predict") <- predict
Expand Down Expand Up @@ -318,7 +323,9 @@ get_emmeans <- function(
}
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]]
colnames(means)[colnames(means) == estimate_name] <- .emmeans_ordinal_estimate_names[[
ordinal_mode
]]
}
.clean_pseudo_factor_emmeans_ordinal(
means,
Expand Down Expand Up @@ -603,7 +610,11 @@ get_emmeans <- function(
# we cannot use the response in `by` (or `contrast`) for the other modes.
# `ordinal_mode` is the mode the user asked for, not the mode that we use
# internally for slopes
.check_ordinal_response_filter <- function(response_filter, ordinal_mode, argument = "by") {
.check_ordinal_response_filter <- function(
response_filter,
ordinal_mode,
argument = "by"
) {
if (!is.null(response_filter) && !identical(ordinal_mode, "prob")) {
insight::format_error(paste0(
"The response `",
Expand Down
22 changes: 18 additions & 4 deletions R/get_marginalcontrasts.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ get_marginalcontrasts <- function(
) {
# check if available
insight::check_if_installed("marginaleffects", minimum_version = "0.29.0")
dots <- list(...)

# temporarily overwrite settings that error on "too many" rows
me_option <- getOption("marginaleffects_safe")
Expand Down Expand Up @@ -135,7 +136,10 @@ get_marginalcontrasts <- function(
} else {
slope_filter <- .parse_by_filter(my_args$by)
}
slope_filter <- .present_filter_values(insight::get_data(model, verbose = FALSE), slope_filter)
slope_filter <- .present_filter_values(
insight::get_data(model, verbose = FALSE),
slope_filter
)
# sanity check - we need at least two slopes to compare. We use the
# original `by`, because a group in a `comparison` formula replaces it
if (
Expand Down Expand Up @@ -167,7 +171,6 @@ get_marginalcontrasts <- function(
# the hypothesis compares all slopes, so we cannot filter the contrasts
# later. Instead, we only keep the data rows that pass the filter. The
# slopes are averaged within each group of `by`, so they don't change.
dots <- list(...)
if (
identical(estimate, "average") &&
length(slope_filter) &&
Expand Down Expand Up @@ -228,6 +231,11 @@ get_marginalcontrasts <- function(
# Last step: Save information in attributes --------------------------------
# ---------------------------------------------------------------------------

# handle brms-alias
if (!is.null(dots$re_formula)) {
dots$re.form <- dots$re_formula
}

out <- .add_attributes(
out,
by = my_args$by,
Expand All @@ -244,7 +252,10 @@ get_marginalcontrasts <- function(
context_effects = my_args$context_effects,
iterations = iterations,
keep_iterations = keep_iterations
)
),
# needed for some additional information for mixed models
model = model,
re_form_na = !is.null(dots$re.form) && is.na(dots$re.form)
)
out <- .rename_focal_attributes(out)

Expand Down Expand Up @@ -319,7 +330,10 @@ get_marginalcontrasts <- function(
.parse_by_filter <- function(by) {
by_filter <- list()
for (i in by[grepl("=", by, fixed = TRUE)]) {
filter_value <- insight::trim_ws(unlist(strsplit(i, "=", fixed = TRUE), use.names = FALSE))
filter_value <- insight::trim_ws(unlist(
strsplit(i, "=", fixed = TRUE),
use.names = FALSE
))
if (length(filter_value) > 1) {
value <- .safe(eval(str2lang(filter_value[2])))
if (!is.null(value)) {
Expand Down
47 changes: 44 additions & 3 deletions R/get_marginalmeans.R
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ get_marginalmeans <- function(
# Last step: Save information in attributes --------------------------------
# ---------------------------------------------------------------------------

# handle brms-alias
if (!is.null(dots$re_formula)) {
dots$re.form <- dots$re_formula
}

means <- .add_attributes(
means,
by = my_args$by,
Expand All @@ -329,7 +334,10 @@ get_marginalmeans <- function(
vcov = vcov_means,
equivalence = dots$equivalence
)
)
),
# needed for some additional information for mixed models
model = model,
re_form_na = !is.null(dots$re.form) && is.na(dots$re.form)
)
class(means) <- unique(c("marginaleffects_means", class(means)))

Expand Down Expand Up @@ -673,14 +681,47 @@ get_marginalmeans <- function(
# - renamed_focal: focal terms with the name of an output column, named by
# their old names, with the new names "<name>.1" as values
#' @keywords internal
.add_attributes <- function(x, by = NULL, model_info = NULL, info = NULL) {
.add_attributes <- function(
x,
by = NULL,
model_info = NULL,
info = NULL,
model = NULL,
re_form_na = NULL
) {
attr(x, "at") <- by
attr(x, "by") <- by
attr(x, "model_info") <- model_info

# extract information, required for mixed models handling
if (!is.null(model)) {
response <- insight::find_response(model, combine = FALSE)
re_intercept <- insight::find_random(model, flatten = TRUE, split_nested = TRUE)
} else {
response <- re_form_na <- re_intercept <- NULL
}

# compact list
info <- insight::compact_list(info)

# some special handling for mixed models - we need this information for
# printing an informative table footer.
if (isTRUE(model_info$is_mixed)) {
# if we have mixed models, we don't want the response in the adusted-for
# information, so we check and remove it here, if necessary
if (!is.null(info$adjusted_for) && !is.null(response)) {
info$adjusted_for <- setdiff(info$adjusted_for, response)
}
# we definitely don't want the random-intercept in the "adjusted for" list
info$adjusted_for <- setdiff(info$adjusted_for, re_intercept)
# save information about random effects (cluster/grouping variable)
if (!isTRUE(re_form_na)) {
info$re_intercept <- re_intercept
}
# save info if re.form was NA
info$re_form_na <- isTRUE(re_form_na)
}

if (!is.null(info) && length(info)) {
if (!is.null(info$at_specs$varname)) {
attr(x, "focal_terms") <- info$at_specs$varname
Expand All @@ -704,7 +745,7 @@ get_marginalmeans <- function(
"contrast", "estimate", "p_adjust", "transform", "transform_arg", "datagrid",
"preserve_range", "coef_name", "slope", "ci", "model_info", "contrast_filter", "null",
"iterations", "keep_iterations", "joint_test", "omnibus_test", "vcov",
"equivalence", "context_effects", "renamed_focal"
"equivalence", "context_effects", "renamed_focal", "re_intercept", "re_form_na"
)
}

Expand Down
Loading
Loading