Skip to content

Commit 204d668

Browse files
authored
Example doesn't work with estimate = "average" (#540)
* Example doesn't work with `estimate = "average"` Fixes #539 * add test * fix * whitespace
1 parent 8439ec5 commit 204d668

File tree

10 files changed

+143
-39
lines changed

10 files changed

+143
-39
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: modelbased
33
Title: Estimation of Model-Based Predictions, Contrasts and Means
4-
Version: 0.12.0.6
4+
Version: 0.12.0.7
55
Authors@R:
66
c(person(given = "Dominique",
77
family = "Makowski",

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
been removed, because this was slightly misleading. The units were in %-points
2424
if multiplied by 100, but this multiplication was not done in the output.
2525

26+
* Improved documentation and improved informative messages.
27+
2628
## Bug fixes
2729

2830
* Fixed issue with `by` in `estimate_contrasts()` when `comparison` was

R/estimate_means.R

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,20 @@
6969
#' (causal inference, see _Chatton and Rohrer 2024_).
7070
#'
7171
#' You can set a default option for the `estimate` argument via `options()`,
72-
#' e.g. `options(modelbased_estimate = "average")`. When you set `estimate` to
73-
#' `"average"`, it calculates the average based only on the data points that
74-
#' actually exist. This is in particular important for two or more focal
75-
#' predictors, because it doesn't generate a *complete* grid of all theoretical
76-
#' combinations of predictor values. Consequently, the output may not include
77-
#' all the values. `estimate = "population"` is not available for
78-
#' `estimate_slopes()`.
72+
#' e.g. `options(modelbased_estimate = "average")`.
73+
#'
74+
#' Note following limitations:
75+
#' - When you set `estimate` to `"average"`, it calculates the average based
76+
#' only on the data points that actually exist. This is in particular
77+
#' important for two or more focal predictors, because it doesn't generate a
78+
#' *complete* grid of all theoretical combinations of predictor values.
79+
#' Consequently, the output may not include all the values.
80+
#' - Filtering the output at values of continuous predictors, e.g.
81+
#' `by = "x=1:5"`, in combination with `estimate = "average"` may result in
82+
#' returning an empty data frame because of what was described above. In such
83+
#' case, you can use `estimate = "typical"` or use the `newdata` argument to
84+
#' provide a data grid of predictor values at which to evaluate predictions.
85+
#' - `estimate = "population"` is not available for `estimate_slopes()`.
7986
#' @param backend Whether to use `"marginaleffects"` (default) or `"emmeans"` as
8087
#' a backend. Results are usually very similar. The major difference will be
8188
#' found for mixed models, where `backend = "marginaleffects"` will also average

R/get_marginalcontrasts.R

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,7 @@ get_marginalcontrasts <- function(
202202
}
203203
# sanity check - do we have any rows left?
204204
if (nrow(out) == 0) {
205-
insight::format_error(
206-
"No rows left after filtering. Please check your `by` and `contrast` arguments, or use a different option for the `estimate` argument, e.g. `estimate = \"typical\"."
207-
)
205+
.filter_error("No rows left after filtering.")
208206
}
209207
}
210208
out

R/get_marginalmeans.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,11 @@ get_marginalmeans <- function(model,
266266
insight::format_error(.marginaleffects_errors(out, fun_args))
267267
}
268268

269+
# check number of rows - for estimate = "average", no rows might be returned
270+
if (nrow(out) == 0) {
271+
.filter_error("No rows returned from marginal means.")
272+
}
273+
269274
out
270275
}
271276

@@ -438,11 +443,26 @@ get_marginalmeans <- function(model,
438443
}
439444
# else, filter values
440445
means <- datawizard::data_match(means, datagrid[datagrid_info$at_specs$varname])
446+
# sanity check - do we have any rows left?
447+
if (nrow(means) == 0) {
448+
.filter_error("No rows left after filtering.")
449+
}
441450
}
442451
means
443452
}
444453

445454

455+
# small helper, because we have the same error message in several places
456+
.filter_error <- function(prefix = "") {
457+
insight::format_error(
458+
prefix,
459+
"Please check your `by` and `contrast` arguments, or try one of the following options:",
460+
"1. Use a different option for the `estimate` argument, e.g. `estimate = \"typical\"`.",
461+
"2. Use the `newdata` argument to provide a data grid of predictor values at which to evaluate predictions."
462+
)
463+
}
464+
465+
446466
# handle attributes -----------------------------------------------------------
447467

448468
# we have following attributes for modelbased-objects:

man/estimate_contrasts.Rd

Lines changed: 16 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/estimate_means.Rd

Lines changed: 16 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/estimate_slopes.Rd

Lines changed: 16 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/get_emmeans.Rd

Lines changed: 16 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-estimate_filter.R

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,44 @@ test_that("special filtering for by and contrast works", {
6565
# )
6666
# )
6767
})
68+
69+
70+
test_that("filtering throws informative error when zero rows are returned", {
71+
set.seed(1234)
72+
n <- 365
73+
event_start <- 200
74+
time <- seq_len(n)
75+
event <- c(rep_len(0, event_start), rep_len(1, n - event_start))
76+
outcome <- 10 + # 1. Pre-intervention intercept
77+
15 * time + # 2. Pre-intervention slope (trend)
78+
20 * event + # 3. Level change (a jump of +20)
79+
5 * event * time + # 4. Slope change (slope becomes 15 + 5 = 20)
80+
rnorm(n, mean = 0, sd = 100) # Add some random noise
81+
82+
dat <- data.frame(outcome, time, event)
83+
mod <- lm(outcome ~ time * event, data = dat)
84+
expect_error(
85+
estimate_contrasts(mod, contrast = "event", by = "time=200", estimate = "average"),
86+
regex = "No rows returned from marginal means"
87+
)
88+
expect_silent(estimate_contrasts(mod, contrast = "event", by = "time=200"))
89+
90+
d <- data.frame(event = c(0, 1), time = 200)
91+
expect_silent(estimate_contrasts(
92+
mod,
93+
contrast = "event",
94+
by = "time",
95+
newdata = d,
96+
estimate = "average"
97+
))
98+
99+
out1 <- estimate_contrasts(mod, contrast = "event", by = "time=200")
100+
out2 <- estimate_contrasts(
101+
mod,
102+
contrast = "event",
103+
by = "time",
104+
newdata = d,
105+
estimate = "average"
106+
)
107+
expect_equal(out1$Difference, out2$Difference, tolerance = 1e-4)
108+
})

0 commit comments

Comments
 (0)