Guided analysis and reproducible power calculations

This tutorial moves from an explicit question to a saved power specification. It runs offline with the installed package. All design values are hypothetical; a subject-matter justification of the effect and sampling design remains necessary.

Describe the question

The advisor records explicit choices. It does not infer a study design from data. At each step, inspect the available option identifiers before answering.

session <- advisor_session()
advisor_question(session)
#> <advisor_question> q.aim (en)
#>   What is the main aim of the analysis?
#>   [describe] Describe variables (distributions, frequencies, summaries) without testing a hypothesis
#>   [compare] Compare an outcome between groups, conditions or time points (a difference)
#>   [associate] Quantify the association between two variables without designating one as the outcome
#>   [model] Estimate how an outcome depends on one or more predictors, possibly adjusted for others (regression)
#>   [predict] Develop a model that predicts an outcome for new individuals
#>   [performance] Evaluate how well an existing score, test or classifier identifies a binary condition, or choose a cutoff
#>   [structure] Explore structure without a designated outcome: summarise many variables into dimensions, or group cases
#>   [causal] Estimate the causal effect of an exposure or intervention
#>   [dont_know] I do not know
session <- advisor_answer(session, "compare")
advisor_question(session)
#> <advisor_question> q.stage (en)
#>   Are you planning a future study or analysing data that already exist?
#>   [planning] Planning a future study (sample size, power)
#>   [analysis] Analysing existing data
#>   [both] Both: planning a study together with its analysis plan
#>   [dont_know] I do not know

The returned session is a new object. Keep it if you want to continue or return to an earlier answer. A partially answered session is not a completed analysis recommendation. advisor_save() and advisor_restore() support a longer session; review any script from advisor_r_code() before executing it.

Make the assumptions explicit

For the calculation below, assume two independent normal populations with equal variances, equal allocation, and a prespecified standardised mean difference of 0.5. These assumptions select the pooled-variance two-sample t procedure. They do not describe paired, clustered or unequal-variance observations.

spec <- analysis_spec(
  "t_means_two_independent", "a_priori",
  inputs = list(alpha = 0.05, power = 0.80, d = 0.5),
  options = list(tails = "two")
)
spec
#> <power_spec> t_means_two_independent | a_priori
#>   method:    Means: Difference between two independent means (two groups)
#>   solve for: n_total
#>   inputs:    d = 0.5, alpha = 0.05, power = 0.8, allocation_ratio = 1 (default)
#>   options:   tails = two
#>   validated against: method definition
#>   spec hash: b370822fcf65844eb1a72100fe306057dcb019f5da1c1c108dbe4b82fb2982c9

alpha is the two-sided type-I error probability; power is the target rejection probability under the specified alternative. The effect size is the difference in population means divided by their common standard deviation. An a priori specification asks the solver for sample size, so do not also supply n_total.

Inspect the result before using it

result <- solve_design(spec)
result
#> <power_result> t_means_two_independent | a_priori
#>   status:      ok
#>   solved:      n_total = 128
#>   achieved:    alpha_attained = 0.05; beta = 0.1985404; power = 0.8014596
#>   group sizes: n1 = 64; n2 = 64
#>   quantities:  alpha_nominal = 0.05; critical = -1.978971,  1.978971; df = 126; ncp = 2.828427
#>   validation:  independently_checked; G*Power reference: reference_pending
#>   spec hash:   b370822fcf65844eb1a72100fe306057dcb019f5da1c1c108dbe4b82fb2982c9
stopifnot(result$status$code == "ok")
result$group_sizes
#> n1 n2 
#> 64 64
result$assumptions
#> [1] "two independent samples from normal populations with equal variances (pooled-variance Student t test; G*Power offers no Welch variant)"
#> [2] "d = (mu1 - mu2) / sigma; df = n1 + n2 - 2; ncp = d * sqrt(n1 * n2 / (n1 + n2)) (manual 21.5)"                                          
#> [3] "two-sided test: alpha/2 in each tail; the power counts both rejection tails (G*Power convention, manual 25.5)"
result$warnings
#> character(0)
result$validation
#> $documented_example_ids
#>  [1] "DOC-001" "DOC-002" "DOC-003" "DOC-005" "DOC-006" "DOC-007" "DOC-008"
#>  [8] "DOC-009" "DOC-010" "DOC-011" "DOC-012" "DOC-013" "DOC-014" "DOC-015"
#> [15] "DOC-016" "DOC-017" "DOC-098" "DOC-099"
#> 
#> $fixture_ids
#> character(0)
#> 
#> $gpower_reference_status
#> [1] "reference_pending"
#> 
#> $independent_checker
#> [1] "validation/independent/t/check_t_means_two_independent.R"
#> 
#> $method_validation_status
#> [1] "independently_checked"

solved$n_total is the combined sample size; group_sizes gives the allocation. achieved$power can exceed the target because only admissible integer designs are considered. The ok calculation status and the method’s reference-validation status answer different questions. Neither establishes clinical utility or correct study assumptions.

Check an invalid request

Constructor errors and numerical outcomes are distinct. Here the error is expected because a probability is outside its domain.

problem <- tryCatch(
  analysis_spec("t_means_two_independent", "a_priori",
    inputs = list(alpha = 1.5, power = 0.8, d = 0.5)),
  error = function(e) conditionMessage(e)
)
problem
#> [1] "invalid specification for t_means_two_independent (a_priori): 'alpha' must lie in (0, 1) (got 1.5)"
stopifnot(is.character(problem))

For other analysis modes use calculate_power(). Inspect ?analysis_spec for mode-specific targets and list_methods() for available procedures; a registry entry alone does not establish that a numerical engine is implemented.

Save a specification and verify its round trip

path <- tempfile(fileext = ".json")
writeLines(ig_to_json(spec), path)
restored <- ig_from_json(file = path)
stopifnot(identical(ig_hash(spec), ig_hash(restored)))
replayed <- solve_design(restored)
stopifnot(identical(result$solved, replayed$solved))

Save the result as well as its specification when archiving an analysis. Record the package revision and execution environment: a matching content hash proves identity of that content, not scientific validation across software versions.

Continue

The classification tutorial introduces denominators and explicit operating points. To explore the same public functions interactively:

install.packages(c("shiny", "ggplot2"))
launch_app()

The launch chunk is not evaluated during a documentation build because it starts an interactive server. No external reference application is required.

sessionInfo()
#> R version 4.6.2 (2026-10-29)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] inferguidR_0.0.0.9000 rmarkdown_2.32       
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.39      R6_2.6.1           fastmap_1.2.0      xfun_0.62         
#>  [5] maketools_1.3.2    cachem_1.1.0       knitr_1.52         htmltools_0.5.9   
#>  [9] buildtools_1.0.0   lifecycle_1.0.5    cli_3.6.6          jsonvalidate_1.5.0
#> [13] sass_0.4.10        jquerylib_0.1.4    compiler_4.6.2     sys_3.4.3         
#> [17] tools_4.6.2        curl_8.1.0         evaluate_1.0.5     bslib_0.12.0      
#> [21] Rcpp_1.1.2         yaml_2.3.12        otel_0.2.0         V8_8.2.1          
#> [25] jsonlite_2.0.0     rlang_1.3.0