Schema and parameters from Shin, Hart, and Simmons (2024) —
multi-study word-problem-solving meta-analysis with four nesting levels
(study / cluster / case / session). See wp_data.csv at OSF 8WBTA.
Data
library(scdbayes)
make_study <- function(study_id, n_cases, base_mean, gain, complexity) {
do.call(rbind, lapply(seq_len(n_cases), function(i) {
n_a <- 4 + 2 * (i - 1)
n_b <- 16 - n_a
data.frame(
study = study_id,
cluster = 1,
case = paste0(study_id, "_C", i),
phase = c(rep("A", n_a), rep("B", n_b)),
time = seq_len(16),
complexityMeasure = complexity,
outcome = round(c(
rnorm(n_a, base_mean, 4),
rnorm(n_b, base_mean + gain, 5)
))
)
}))
}
wp_meta <- rbind(
make_study("S1", 3, 32, 28, "singleType"),
make_study("S2", 3, 28, 22, "mixedType"),
make_study("S3", 3, 35, 18, "generalization"),
make_study("S4", 3, 30, 30, "singleType")
)
head(wp_meta)
#> study cluster case phase time complexityMeasure outcome
#> 1 S1 1 S1_C1 A 1 singleType 34
#> 2 S1 1 S1_C1 A 2 singleType 28
#> 3 S1 1 S1_C1 A 3 singleType 33
#> 4 S1 1 S1_C1 A 4 singleType 32
#> 5 S1 1 S1_C1 B 5 singleType 57
#> 6 S1 1 S1_C1 B 6 singleType 47Columns
The hierarchy is read directly from named columns — no detection step
is needed: study, cluster, case,
phase ("A"/"B"),
time, outcome, and
complexityMeasure (a study-level moderator). Pass these
names directly to the functions below.
Hierarchy levels
detect_hierarchy_levels(wp_meta)[c("structure", "n_level1",
"n_level2", "n_level3")]
#> $structure
#> [1] "three_level_no_cluster"
#>
#> $n_level1
#> [1] 192
#>
#> $n_level2
#> [1] 12
#>
#> $n_level3
#> [1] 4Meta summary
calculate_meta_summary(wp_meta,
structure = list(levels = list(study = "study")))
#> $n_studies
#> [1] 4
#>
#> $n_effects
#> [1] 192
#>
#> $total_participants
#> [1] 12
#>
#> $effects_per_study
#> $effects_per_study$min
#> [1] 48
#>
#> $effects_per_study$mean
#> [1] 48
#>
#> $effects_per_study$median
#> [1] 48
#>
#> $effects_per_study$max
#> [1] 48Covariates
analyze_covariates(wp_meta,
structure = list(levels = list(study = "study",
case = "case")))$categorical$complexityMeasure
#> $levels
#> [1] "generalization" "mixedType" "singleType"
#>
#> $frequencies
#> [1] 48 48 96
#>
#> $proportions
#>
#> generalization mixedType singleType
#> 25 25 50Phase (piecewise) coding
wp_meta_p <- process_scd_phases(wp_meta, structure = list(),
scd_type = "multiple_baseline")
head(wp_meta_p[, c("study", "case", "time", "phase",
"level_AB", "time_A", "trend_AB", "outcome")], 4)
#> # A tibble: 4 × 8
#> study case time phase level_AB time_A trend_AB outcome
#> <chr> <chr> <int> <chr> <int> <dbl> <dbl> <dbl>
#> 1 S1 S1_C1 1 A 0 0 0 34
#> 2 S1 S1_C2 1 A 0 0 0 34
#> 3 S1 S1_C3 1 A 0 0 0 31
#> 4 S2 S2_C1 1 A 0 0 0 31Random-effects structure
build_default_random_effects(wp_meta_p, scd_type = "ab",
case_var = "case", study_var = "study")
#> [1] "(1 + level_AB | study) + (1 + level_AB | case) + (1 + level_AB | case:complexityMeasure)"The app’s Model > Random Effects tab exposes the same structure as
four checkboxes (case/study intercept and slope). With all four on it
emits the equivalent
(1 + phase | case) + (1 + phase | study); the random
coefficient is placed on the level-change term level_AB
(the immediate phase shift) once phases are piecewise-coded, distinct
from the trend-change term trend_AB.
Three-level model
three_level <- outcome ~ time_A + level_AB + trend_AB +
(level_AB | study) +
(level_AB | case)
three_level
#> outcome ~ time_A + level_AB + trend_AB + (level_AB | study) +
#> (level_AB | case)Four-level model
four_level <- outcome ~ time_A + level_AB + trend_AB +
complexityMeasure +
complexityMeasure:level_AB +
complexityMeasure:trend_AB +
(level_AB | study/cluster/case)
four_level
#> outcome ~ time_A + level_AB + trend_AB + complexityMeasure +
#> complexityMeasure:level_AB + complexityMeasure:trend_AB +
#> (level_AB | study/cluster/case)Fitting the Bayesian model
opts <- list(formula = three_level, family = "gaussian",
prior_type = "default")
if (interactive()) {
result <- run_scd_analysis(wp_meta_p, options = opts)
result$diagnostics$converged
summarize_fixed_effect_decisions(result$model)
}Per-case decisions
With case-level random effects, each case carries its own posterior.
The app’s Per Study tab and summarize_per_case_effects()
report these case-specific decisions.
if (interactive()) {
summarize_per_case_effects(result$model,
intervention_goal = "increase")
}Source study
- Shin, M., Hart, S. L., & Simmons, M. (2024). Meta-analysis of single-case design research: Application of multilevel modeling. School Psychology, 39(6), 625-635. https://doi.org/10.1037/spq0000637
