Skip to content
Merged
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.14.0.8
Version: 0.14.0.9
Authors@R:
c(person(given = "Dominique",
family = "Makowski",
Expand Down
7 changes: 4 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@

* Support for models of class `nestedLogit`.

* Added option `comparison = "slope"` to `estimate_contrast()`, to allow
calculating contrasts of average slopes. This can also be useful when
estimating the context effects of within- and between-effects in a model.
* `estimate_contrasts()` can now also calculate contrasts of average slopes, by
defining two numeric predictors in the `contrast` argument. This can also be
useful when estimating the context effects of within- and between-effects in a
model.

# modelbased 0.14.0

Expand Down
82 changes: 41 additions & 41 deletions R/estimate_contrasts.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
#' @param contrast A character vector indicating the name of the variable(s) for
#' which to compute the contrasts, optionally including representative values or
#' levels at which contrasts are evaluated (e.g., `contrast="x=c('a','b')"`).
#' **Note:** It is also possible to contrast average slopes, i.e. `contrast` can
#' be the name of two numeric predictors. However, while it is possible to
#' filter data for one numeric contrast (e.g., `contrast = c("num_pred=c(0, 1, 3)")`),
#' it is not possible to "filter" at certain values for two numeric predictors.
#' For contrasting slopes, the `comparison` will always be `"pairwise"`. It is
#' possible to compute pairwise comparisons of two average slopes at the levels
#' of a third variable, by also adding that variable to the `contrast` argument,
#' e.g. `contrast = c("num1", "num2", "factor")`. See 'Examples'.
#' @param p_adjust The p-values adjustment method for frequentist multiple
#' comparisons. Can be one of `"none"` (default), `"hochberg"`, `"hommel"`,
#' `"bonferroni"`, `"BH"`, `"BY"`, `"fdr"`, `"tukey"`, `"sidak"`, `"sup-t"`,
Expand Down Expand Up @@ -53,25 +61,20 @@
#' computes pairwise differences of relative inequality measures (ratios).
#' See an overview of applications in the related case study in the
#' [vignettes](https://easystats.github.io/modelbased/articles/practical_inequalities.html).
#' * String: Additional special string options are `"slope"` and
#' `"slope_pairwise"`. `comparison ="slope"` calculates contrasts between
#' average slopes and can also be used to calculate "context" effects, which
#' is the difference of within- and between-effects (see
#' https://statisticalhorizons.com/between-within-contextual-effects/).
#' `comparison ="slope_paiwirse"` returns pairwise comparisons of such
#' context effects, which can be used when `by` is used to stratify results
#' by the levels of another variable.
#' * String equation: To identify parameters from the output, either specify
#' the term name, or `"b1"`, `"b2"` etc. to indicate rows, e.g.:`"hp = drat"`,
#' `"b1 = b2"`, or `"b1 + b2 + b3 = 0"`.
#' * Formula: A formula like `comparison ~ pairs | group`, where the left-hand
#' side indicates the type of comparison (`difference` or `ratio`), the
#' * Formula: A formula like `<comparison> ~ pairs | group`, where the left-hand
#' side indicates the type of `<comparison>` (`difference` or `ratio`), the
#' right-hand side determines the pairs of estimates to compare (`reference`,
#' `sequential`, `meandev`, etc., see string-options). Optionally, comparisons
#' can be carried out within subsets by indicating the grouping variable
#' after a vertical bar ( `|`).
#' after a vertical bar ( `|`). If the left-hand side is missing, it defaults
#' to `difference` (i.e. `comparison = ~pairs | group` is identical to
#' `comparison = difference ~ pairs | group`).
#' * A custom function, e.g. `comparison = myfun`, or
#' `comparison ~ I(my_fun(x)) | groups`.
#' `<comparison> ~ I(my_fun(x)) | groups` (where `<comparison>` can be
#' `difference` or `ratio`, or skipped).
#' * If contrasts should be calculated (or grouped by) factors, `comparison`
#' can also be a matrix that specifies factor contrasts (see 'Examples').
#' @param effectsize Desired measure of standardized effect size, one of
Expand Down Expand Up @@ -117,21 +120,6 @@
#' - `comparison = "trt_vs_ctrl"`: This compares all levels (excluding the
#' first, which is typically the control) against the first level. It's often
#' used when comparing multiple treatment groups to a single control group.
#' - `comparison = "slope"`: This calculates contrasts between average slopes.
#' An interesting use-case is the calculation of "context" effects when
#' modelling within- and between-effects. An example for within- and between
#' effects is described [in this vignette](https://easystats.github.io/parameters/articles/demean.html).
#' A context effect describes the additional influence that the social or
#' regional environment (e.g., place of residence) has on an individual,
#' independent of their personal characteristics. It demonstrates that people
#' with identical individual circumstances (such as the same income) face
#' different opportunities or risks depending on the environment in which they
#' live.
#' - `comparison = "slope_pairwise"`: This returns pairwise comparisons of
#' context effects (i.e., the pairwise comparisons of the difference of
#' between within- and between-effects, or the difference of average slopes).
#' This can be used when `by` is used to stratify results by the levels of
#' another variable.
#' - To test multiple hypotheses jointly (usually used for factorial designs),
#' `comparison` can also be `"joint"`. In this case, use the `test` argument
#' to specify which test should be conducted: `"F"` (default) or `"Chi2"`.
Expand Down Expand Up @@ -164,6 +152,27 @@
#' Examples for analysing inequalities are shown in the related
#' [vignette](https://easystats.github.io/modelbased/articles/practical_inequalities.html).
#'
#' @section Context Effects - contrasting average slopes:
#' Calculating contrasts between average slopes can tell us about the
#' "context" effects when modelling within- and between-effects. An example
#' for within- and between effects is described
#' [in this vignette](https://easystats.github.io/parameters/articles/demean.html).
#' A context effect describes the additional influence that the social or
#' regional environment (e.g., place of residence) has on an individual,
#' independent of their personal characteristics. It demonstrates that people
#' with identical individual circumstances (such as the same income) face
#' different opportunities or risks depending on the environment in which they
#' live. This can be achieved by specifying both numeric predictors in the
#' `contrast` argument, e.g. `contrast = c("x_within", "x_between")`. It is
#' also possible to stratify context effects at different levels of another
#' variable using `by`. If pairwise comparisons of context effects (i.e., the
#' pairwise comparisons of the difference of between within- and
#' between-effects, or the difference of average slopes) at different levels
#' of another variable are required, add that variable to the `contrast`
#' argument instead, e.g. `contrast = c("x_within", "x_between", "factor")`.
#' Note that when average slopes are contrasted, the `comparison` argument has
#' no effect and is always set to `"pairwise"`. See also 'Examples'.
#'
#' @section Effect Size:
#'
#' By default, `estimate_contrasts()` reports no standardized effect size on
Expand Down Expand Up @@ -318,22 +327,13 @@
#' # context effect (difference between within- and between-effect)
#' # at each time point - we calculate the contrast of two average slopes
#' # at different levels of "time"
#' estimate_contrasts(
#' model,
#' c("phq4_within", "phq4_between"),
#' by = "time",
#' comparison = "slope"
#' )
#' estimate_contrasts(model, c("phq4_within", "phq4_between"), by = "time")
#'
#' # is the difference of the context effect between the time points
#' # statistically significant? We need pairwise comparisons of contrasts
#' # of slopes to calculate this
#' estimate_contrasts(
#' model,
#' c("phq4_within", "phq4_between"),
#' by = "time",
#' comparison = "slope_pairwise"
#' )
#' # statistically significant? We want pairwise comparisons of contrasts
#' # of slopes to calculate this (i.e. contrasts of average slopes at pairs
#' # of levels of "time")
#' estimate_contrasts(model, c("phq4_within", "phq4_between", "time"))
#' }
#'
#' @return A data frame of estimated contrasts.
Expand Down
2 changes: 1 addition & 1 deletion R/format.R
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ equivalence_columns <- c(
if (
comparison_hypothesis %in%
c("inequality", "inequality_ratio") &&
isTRUE(attributes(x)$compute_slopes)
isTRUE(attributes(x)$contrast_slopes)
) {
# for slopes, we either have the trend variable, or only the grouping,
# but not the "inequality" variabe (the first in "by"). Update labels,
Expand Down
4 changes: 2 additions & 2 deletions R/get_contexteffects.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
# if we have stratified by another group, we need the difference between
# contrasts at each group level
if (is.null(my_args$by)) {
comparison <- as.formula("~I(diff(x))")
comparison <- stats::as.formula("~I(diff(x))")
} else {
comparison <- as.formula(paste("~I(diff(x)) |", my_args$by))
comparison <- stats::as.formula(paste("~I(diff(x)) |", my_args$by))
}

# prepare arguments
Expand Down
16 changes: 4 additions & 12 deletions R/get_inequalitycontrasts.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
my_args,
comparison,
ci,
compute_slopes = FALSE,
estimate = NULL,
...
) {
Expand Down Expand Up @@ -38,14 +37,14 @@
# to average over other variables in `by`, we use the formula interface.
# For example, `by = c("grp1", "gpr2")` and `comparison = ~ inequality | grp2`
# would average over `grp1` and calculate pairwise comparisons for `grp2`.
if (is.null(my_args$by) || (length(my_args$by) == 1 && compute_slopes)) {
if (is.null(my_args$by) || (length(my_args$by) == 1 && my_args$contrast_slopes)) {
group <- NULL
} else if (inherits(comparison, "formula")) {
# groups in formula interface are used for grouping
out <- .process_inequality_formula(comparison)
comparison <- out$comparison
group <- out$group
} else if (compute_slopes) {
} else if (my_args$contrast_slopes) {
# `by` is used for grouping, but first `by` element is ignored for slopes.
# we need the fist element in `by` for contrasting slopes at a predictor
group <- my_args$by[-1]
Expand All @@ -58,7 +57,7 @@
# inequality comparisons for slopes -------------------------
# -----------------------------------------------------------

if (compute_slopes) {
if (my_args$contrast_slopes) {
# marginal effects inequalities for slopes.
# we need a `by` argument, otherwise, pairwise comparisons of slopes for all
# combinations of values of the `trend` variable would be calculated.
Expand All @@ -85,7 +84,7 @@
# save some labels for printing
attr(out, "by") <- my_args$by
attr(out, "trend") <- my_args$contrast
attr(out, "compute_slopes") <- TRUE
attr(out, "contrast_slopes") <- TRUE
} else {
# -----------------------------------------------------------
# inequality comparisons for categorical predictors ---------
Expand Down Expand Up @@ -260,13 +259,6 @@
return(out)
}
}
# handle special value: contrasting average slopes (context effects)
if (identical(comparison, "slope")) {
comparison <- "context"
}
if (identical(comparison, "slope_pairwise")) {
comparison <- "context_pairwise"
}
comparison
}

Expand Down
97 changes: 73 additions & 24 deletions R/get_marginalcontrasts.R
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,11 @@ get_marginalcontrasts <- function(
my_args,
model_data,
estimate,
on_the_fly_factors,
verbose,
...
)

# extract first focal term
first_focal <- my_args$contrast[1]

# sanity check - is it a list? if so, use name
if (is.list(first_focal)) {
first_focal <- names(first_focal)
}

# sanity check: `contrast` and `by` cannot be the same
if (
length(my_args$cleaned_by) &&
Expand All @@ -75,13 +69,6 @@ get_marginalcontrasts <- function(
)
}

# check whether we contrast slopes or categorical predictors:
# if first focal term is numeric, we contrast slopes, but slopes only for
# numerics with many values, not for binary or likert-alike
compute_slopes <- is.numeric(model_data[[first_focal]]) &&
!.is_likert(model_data[[first_focal]], verbose = verbose, ...) &&
!first_focal %in% on_the_fly_factors

# Second step: compute contrasts, for slopes or categorical -----------------
# ---------------------------------------------------------------------------

Expand All @@ -96,7 +83,6 @@ get_marginalcontrasts <- function(
my_args,
comparison,
ci,
compute_slopes,
estimate,
...
)
Expand All @@ -110,11 +96,11 @@ get_marginalcontrasts <- function(
transform <- "exp"
}
}
} else if (compute_slopes) {
} else if (my_args$contrast_slopes) {
# sanity check - contrast for slopes only makes sense when we have a "by" argument
if (is.null(my_args$by)) {
insight::format_error(
"Please specify the `by` argument to calculate contrasts of slopes. If you want to calculate the average contrast between two slopes, use `comparison = \"slope\"` instead."
"Please specify the `by` argument to calculate contrasts of slopes."
)
}
# call slopes with hypothesis argument
Expand Down Expand Up @@ -242,16 +228,15 @@ get_marginalcontrasts <- function(
my_args,
model_data = NULL,
estimate = NULL,
on_the_fly_factors = NULL,
verbose = TRUE,
...
) {
# init
comparison_slopes <- by_filter <- contrast_filter <- by_token <- NULL
joint_test <- FALSE
context_effects <- FALSE
# overwrite "comparison" when it's set to "context".
if (identical(comparison, "context") || identical(comparison, "context_pairwise")) {
context_effects <- TRUE
}

# save original `by`
original_by <- my_args$by
original_comparison <- comparison
Expand Down Expand Up @@ -302,7 +287,6 @@ get_marginalcontrasts <- function(
!is.null(my_args$contrast) &&
any(grepl("=", my_args$contrast, fixed = TRUE))
) {
# nolint
# find which element in `by` has a filter
filter_index <- grep("=", my_args$contrast, fixed = TRUE)
for (f in filter_index) {
Expand All @@ -322,6 +306,69 @@ get_marginalcontrasts <- function(
}
}

# -----------------------------------------------------------------------
# here we check whether user wants to compute contrasts of slopes by group, or
# even contrasts of two average slopes (context effects).
# -----------------------------------------------------------------------

# extract first focal term
first_focal <- my_args$contrast[1]

# extract second focal term - if we have two numeric focal terms, we
# calculate "context" effects (the contrast of average slopes), see
# .get_contexteffects()
if (length(my_args$contrast) > 1) {
second_focal <- my_args$contrast[2]
} else {
second_focal <- NULL
}

# sanity check - is it a list? if so, use name
if (is.list(first_focal)) {
first_focal <- names(first_focal)
}
if (is.list(second_focal)) {
second_focal <- names(second_focal)
}

# check whether we contrast slopes or categorical predictors:
# if first focal term is numeric, we contrast slopes, but slopes only for
# numerics with many values, not for binary or likert-alike
contrast_slopes <- is.numeric(model_data[[first_focal]]) &&
!.is_likert(model_data[[first_focal]], verbose = verbose, ...) &&
!first_focal %in% on_the_fly_factors

# check whether both focal terms are numeric, in which case we calculate
# the contrasts of two average slopes (context effects). We could skip this
# check when the user uses `comparison = "slope"`, however, this is not
# straightforward. It's better to allow users to define two numeric variables
# for contrasting, without the need to care about special comparison-options
if (
!is.null(second_focal) &&
is.numeric(model_data[[second_focal]]) &&
!.is_likert(model_data[[second_focal]], verbose = verbose, ...) &&
!second_focal %in% on_the_fly_factors &&
contrast_slopes
) {
# "pairwise" argument will be ignored for context effects
if (!identical(comparison, "pairwise")) {
insight::format_alert(
"The `comparison` argument will be set to `\"pairwise\"` when contrasting average slopes."
)
}
# overwrite some of the previous arguments
context_effects <- TRUE
# if we have no "by" variable, user doesn't want to stratify, so set to
# pairwise comparisons of categorical variable
if (is.null(my_args$by) && length(my_args$contrast) > 2) {
comparison <- "context_pairwise"
my_args$by <- my_args$contrast[3:length(my_args$contrast)]
my_args$contrast <- my_args$contrast[1:2]
} else {
comparison <- "context"
}
Comment thread
strengejacke marked this conversation as resolved.
}

# convert comparison and by into a formula
if (is.null(comparison)) {
# default to pairwise, if comparison = NULL
Expand Down Expand Up @@ -428,7 +475,9 @@ get_marginalcontrasts <- function(
context_effects = context_effects,
# cleaned `by` and `contrast`, without filtering information
cleaned_by = gsub("=.*", "\\1", my_args$by),
cleaned_contrast = gsub("=.*", "\\1", my_args$contrast)
cleaned_contrast = gsub("=.*", "\\1", my_args$contrast),
Comment thread
strengejacke marked this conversation as resolved.
# remember whether contrasts of slopes should be calculated
contrast_slopes = contrast_slopes
)
)

Expand Down
Loading
Loading