--- title: "Effect sizes: maximal models, fitted pilots, and safeguard power" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Effect sizes: maximal models, fitted pilots, and safeguard power} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") set.seed(1) ``` The hardest part of a power analysis is rarely the simulation --- it is choosing a defensible effect size and a realistic random-effects structure. This vignette ties together four `mixpower` features that address exactly that: - **maximal models** with one or more correlated random slopes, - **`mp_from_fit()`** to drive power from an existing (pilot or published) fit, - **`mp_sesoi()`** to plan around a *smallest effect size of interest*, and - **`mp_safeguard_effect()`** for an uncertainty-aware, conservative effect, and closes with how to read the **Type S / Type M** diagnostics that `mp_power()` reports for free. ```{r} library(mixpower) ``` ## Maximal models: correlated random slopes A within-participant manipulation usually varies *within* every subject, so the "maximal" random-effects structure includes a random slope for it. Ignoring a real random slope inflates the Type I error rate, so simulating from --- and fitting --- the maximal model gives honest power. `random_effects` takes a per-group `intercept_sd`, an optional named list of `slopes`, and an optional correlation `cor` (a scalar applied to every pair of terms, or a full correlation matrix). Each fixed effect you name also becomes a balanced design predictor, so several effects are crossed as a factorial. ```{r} d <- mp_design(clusters = list(subject = 20), trials_per_cell = 6) a <- mp_assumptions( fixed_effects = list(`(Intercept)` = 0, x1 = 0.5, x2 = 0.3), random_effects = list(subject = list( intercept_sd = 0.4, slopes = list(x1 = 0.3, x2 = 0.3), cor = 0.1 )), residual_sd = 1 ) a ``` ```{r} scn_max <- mp_scenario_lme4( y ~ x1 + x2 + (1 + x1 + x2 | subject), design = d, assumptions = a, predictor = "x1" ) mp_power(scn_max, nsim = 15, seed = 2024)$power ``` For a single within-subject factor the same machinery reduces to the familiar `(1 + condition | subject)` form with one slope and an intercept-slope correlation. ## Power from a fitted pilot model When you have a pilot or published model, `mp_from_fit()` turns it into a scenario: new responses are simulated from the fit (keeping its estimated variance components), the model is refit, and the focal term is tested. The fixed effects start at the fitted coefficients but can be varied for planning. ```{r} m <- lme4::lmer(Reaction ~ Days + (Days | Subject), data = lme4::sleepstudy) scn_fit <- mp_from_fit(m, test_term = "Days") scn_fit$assumptions ``` ```{r} mp_power(scn_fit, nsim = 15, seed = 1)$power ``` ## Planning around a smallest effect size of interest Pilot and published effects are often optimistic. A robust habit is to plan power for the *smallest effect you would still care about*. `mp_sesoi()` returns a copy of a scenario with the focal effect replaced --- either explicitly, or by scaling the assumed effect (the default `multiplier = 0.85` is a conservative 15% reduction). ```{r} scn_sesoi <- mp_sesoi(scn_fit, multiplier = 0.85) c( full = scn_fit$assumptions$fixed_effects$Days, sesoi = scn_sesoi$assumptions$fixed_effects$Days ) ``` Sweeping the multiplier shows how quickly power erodes as the effect of interest shrinks --- the whole point of planning for a SESOI rather than the (large) pilot estimate: ```{r} mults <- c(1, 0.5, 0.3, 0.2) powers <- vapply( mults, function(mult) mp_power(mp_sesoi(scn_fit, multiplier = mult), nsim = 15, seed = 1)$power, numeric(1) ) data.frame(multiplier = mults, effect = mults * scn_fit$assumptions$fixed_effects$Days, power = powers) ``` ## Safeguard power A *safeguard* effect (Perugini, Gallucci & Costantini, 2014) goes one step further: instead of a point estimate, it uses the confidence-interval bound nearest zero, so sampling uncertainty in the pilot is propagated into the plan. `mp_safeguard_effect()` computes it; feed the result straight into `mp_sesoi()`. ```{r} sg <- mp_safeguard_effect(m, term = "Days", conf_level = 0.90) sg ``` ```{r} scn_safe <- mp_sesoi(scn_fit, effect = sg) mp_power(scn_safe, nsim = 15, seed = 1)$power ``` The safeguard effect (the lower confidence bound) is smaller than the point estimate. Here the `sleepstudy` `Days` effect is large enough that even its conservative bound stays well powered; for a borderline effect, that same buffer is exactly what stops an over-optimistic pilot from promising power it cannot deliver --- compare it against the multiplier sweep above. ## Reading Type S and Type M errors Every `mp_power()` run reports two diagnostics that go beyond the power figure (Gelman & Carlin, 2014): - **Type S** (sign): among significant replicates, the fraction with the *wrong* sign. - **Type M** (magnitude): among significant replicates, the average ratio of the estimated to the true effect --- the *exaggeration* factor. At low power, significant estimates are systematically inflated (Type M well above 1); at high power they are well calibrated. ```{r} under <- mp_power(mp_sesoi(scn_fit, multiplier = 0.25), nsim = 25, seed = 7) summary(under) under$diagnostics$type_m ``` A Type M near 1 means significant estimates are about right; a value like 1.5 means published "hits" from this design would overstate the effect by ~50%. This is why planning for a SESOI or safeguard effect --- and powering well --- matters for replicable findings.