| Title: | Simulation-Based Sample Size Tools for Prediction Models |
|---|---|
| Description: | Provides a flexible, simulation-based toolkit for exploring how much data are needed to develop reliable prediction models. It works by repeatedly generating data, fitting models, and evaluating performance to show how sample size affects predictive accuracy, calibration, and overfitting. The package supports continuous, binary, and time-to-event outcomes and can be used with both regression-based modelling approaches and machine-learning methods. It is designed to help researchers plan studies, assess feasibility, and build more robust and generalisable models. The methods are described in Olaniran et al. (2026) <doi:10.1186/s12874-026-02935-9> and Shamsutdinova et al. (2026) <doi:10.48550/arXiv.2602.23507>. |
| Authors: | Ewan Carr [aut, cre] (ORCID: <https://orcid.org/0000-0002-1146-4922>), Gordon Forbes [aut] (ORCID: <https://orcid.org/0000-0001-8662-3636>), Ridwan Olaniran [aut] (ORCID: <https://orcid.org/0000-0001-7342-8639>), Diana Shamsutdinova [aut] (ORCID: <https://orcid.org/0000-0003-2434-3641>), Daniel Stahl [aut] (ORCID: <https://orcid.org/0000-0001-7987-6619>), Sarah Markham [aut] (ORCID: <https://orcid.org/0000-0002-8755-5935>), Felix Zimmer [aut] (ORCID: <https://orcid.org/0000-0002-8127-0007>) |
| Maintainer: | Ewan Carr <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 1.0.0 |
| Built: | 2026-09-18 09:02:29 UTC |
| Source: | https://github.com/pmsims-package/pmsims |
Results from four completed sample-size searches, one for each of the package's entry points. A search repeatedly simulates datasets, fits a prediction model to each and evaluates its performance across a range of candidate sample sizes, so a realistic run takes minutes to hours. These objects were computed once, with the calls shown under Source, so that the examples and the vignette can demonstrate the output without recomputing it.
binary_example continuous_example survival_example custom_examplebinary_example continuous_example survival_example custom_example
A list of class "pmsims". The most useful components are:
min_nThe estimated minimum sample size.
perf_nExpected performance at min_n.
target_performanceThe performance target the search aimed for.
metricThe performance metric used.
modelThe model fitted to each simulated dataset.
mlpwr_dsThe sampled designs and simulated performance values
behind the fitted curve, used by plot().
summariesAggregated performance summaries across replications.
simulation_timeElapsed time of the original run, in seconds.
Objects produced by the wrapper functions additionally record the
data-generating configuration (for example outcome_prevalence,
correlation and complexity); see simulate_binary() for the full set.
An object of class pmsims of length 36.
An object of class pmsims of length 35.
An object of class pmsims of length 37.
An object of class pmsims of length 24.
Each object is a "pmsims" object, as returned by simulate_binary(),
simulate_continuous(), simulate_survival() and simulate_custom(), and
can be used with print() and plot() in the usual way.
Generated by data-raw/precomputed-examples.R.
binary_example:
set.seed(123) simulate_binary( signal_parameters = 20, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0.3), outcome_prevalence = 0.30, maximum_achievable_cstatistic = 0.80, model = "glm", metric = "calibration_slope", target_performance = 0.85, n_reps_total = 1000, mean_or_assurance = "assurance" )
continuous_example:
set.seed(123) simulate_continuous( signal_parameters = 15, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0.3), maximum_achievable_rsquared = 0.50, model = "lm", metric = "calibration_slope", target_performance = 0.95, n_reps_total = 1000, mean_or_assurance = "assurance" )
survival_example:
set.seed(123) simulate_survival( signal_parameters = 15, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0.3), maximum_achievable_cindex = 0.70, baseline_hazard = 0.01, censoring_rate = 0.30, model = "coxph", metric = "calibration_slope", target_performance = 0.90, n_reps_total = 1000, mean_or_assurance = "assurance" )
custom_example: a linear model with three independent predictors and a
population of 0.5, targeting a calibration slope of 0.9 with
80% assurance. The search uses bounds of 25 to 1,000 participants, 1,000
replications and 30,000 test observations. See simulate_custom() for the
generating functions and executable call (seed 123).
simulate_binary(), simulate_continuous(),
simulate_survival(), simulate_custom()
binary_example binary_example$min_nbinary_example binary_example$min_n
Compute the minimum sample size required to develop a prediction model with a
binary outcome. The function wraps a simulation-based engine that combines a
bisection search with Gaussian-process curve fitting. From user inputs
(outcome prevalence, maximum achievable performance, target performance, etc.) it
constructs a data-generating function, a model-fitting
function, and a metric function, then searches for the smallest that
meets the chosen performance criterion.
simulate_binary( signal_parameters, noise_parameters = 0, complexity = 1, data_control = NULL, outcome_prevalence, maximum_achievable_cstatistic, model = c("glm", "lasso", "ridge", "rf", "xgboost"), metric = "calibration_slope", target_performance, n_reps_total = 1000, mean_or_assurance = "assurance", ... )simulate_binary( signal_parameters, noise_parameters = 0, complexity = 1, data_control = NULL, outcome_prevalence, maximum_achievable_cstatistic, model = c("glm", "lasso", "ridge", "rf", "xgboost"), metric = "calibration_slope", target_performance, n_reps_total = 1000, mean_or_assurance = "assurance", ... )
signal_parameters |
Integer. Number of candidate predictors associated with the outcome (i.e., true signal features). |
noise_parameters |
Integer. Number of candidate predictors not associated with the outcome (noise features). Default is 0. |
complexity |
Integer in 1:4 selecting the data-generating signal
structure (see Data control). Default |
data_control |
Optional named list controlling the predictors (see
Data control). Default |
outcome_prevalence |
Numeric in (0, 1). Target prevalence of the binary outcome in the intended modelling context. |
maximum_achievable_cstatistic |
Numeric in (0, 1). Maximum achievable C-statistic with effectively unlimited data. This is used to calibrate the data-generating mechanism and is not the minimum acceptable threshold. |
model |
Character string specifying the modelling algorithm. One of
|
metric |
Character string naming the performance metric used to assess
the sample size; defaults to
|
target_performance |
Numeric. Minimum acceptable value of the selected
performance metric |
n_reps_total |
Integer. Total number of simulation replications used by the engine across the search. |
mean_or_assurance |
Character string, either |
... |
Additional options passed to |
An object of class "pmsims" containing the estimated minimum sample
size and simulation diagnostics (inputs, fitted GP curve, intermediate
evaluations, and summary metrics).
Two formulations are supported.
Mean-based: find the smallest such that the expected model
performance exceeds the target , i.e.
Assurance-based: find the smallest such that the probability
the performance exceeds is at least (e.g. 0.80),
i.e.
Here, is the chosen performance metric and the probability/expectation
is over repeated samples of training data of size . The assurance
criterion explicitly accounts for variability across training sets; models
with higher variance typically require larger to satisfy it.
complexity selects the signal structure of the data-generating mechanism:
1 purely linear, 2 linear + quadratic, 3 linear + quadratic +
interaction, 4 the Friedman function. data_control is an optional list
fine-tuning the predictors:
nonlinear_strengthNumeric in [0, 1). Fraction of the signal
variance carried by the nonlinear, linearly-inaccessible component.
Applies to complexity 2 and 3 only; ignored (with a warning) for 1 and 4.
If omitted, the generator's per-complexity default is used.
correlationNumeric in . Pairwise correlation among the
candidate predictors. Default 0.3.
predictor_distributionOne of "normal", "uniform", "binary",
"exponential", "lognormal", "t", "laplace". "binary" selects
0/1 predictors and requires binary_predictor_prevalence; any other
value selects continuous predictors from that family. Default "normal".
binary_predictor_prevalenceNumeric in (0, 1). Prevalence of the
binary predictors; required when predictor_distribution = "binary",
ignored (with a warning) otherwise. Note: binary predictors are
incompatible with complexity 2/3 because squaring a 0/1 variable returns
itself.
simulate_continuous(), simulate_survival(), simulate_custom()
set.seed(123) est <- simulate_binary( signal_parameters = 3, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0), outcome_prevalence = 0.50, maximum_achievable_cstatistic = 0.80, model = "glm", metric = "calibration_slope", target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 50, max_sample_size = 1000, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)set.seed(123) est <- simulate_binary( signal_parameters = 3, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0), outcome_prevalence = 0.50, maximum_achievable_cstatistic = 0.80, model = "glm", metric = "calibration_slope", target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 50, max_sample_size = 1000, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)
Compute the minimum sample size required to develop a prediction model with a
continuous outcome. This wraps the same simulation engine as
simulate_binary(), combining bisection search with Gaussian-process
learning-curve modelling. From user inputs (maximum achievable performance, target
performance, etc.) it constructs a
data-generating function, model-fitting function, and metric function, then
searches for the smallest meeting the chosen criterion.
simulate_continuous( signal_parameters, noise_parameters = 0, complexity = 1, data_control = NULL, maximum_achievable_rsquared, model = c("lm", "lasso", "ridge", "rf", "xgboost"), metric = "calibration_slope", target_performance, n_reps_total = 1000, mean_or_assurance = "assurance", ... )simulate_continuous( signal_parameters, noise_parameters = 0, complexity = 1, data_control = NULL, maximum_achievable_rsquared, model = c("lm", "lasso", "ridge", "rf", "xgboost"), metric = "calibration_slope", target_performance, n_reps_total = 1000, mean_or_assurance = "assurance", ... )
signal_parameters |
Integer. Number of candidate predictors associated with the outcome (i.e., true signal features). |
noise_parameters |
Integer. Number of candidate predictors not associated with the outcome (noise features). Default is 0. |
complexity |
Integer in 1:4 selecting the data-generating signal
structure (see Data control). Default |
data_control |
Optional named list controlling the predictors (see
Data control). Default |
maximum_achievable_rsquared |
Numeric in (0, 1). Maximum achievable
|
model |
Character string specifying the modelling algorithm. One of
|
metric |
Character string naming the performance metric used to assess
the sample size; defaults to
|
target_performance |
Numeric. Minimum acceptable value of the selected
performance metric |
n_reps_total |
Integer. Total number of simulation replications used by the engine across the search. |
mean_or_assurance |
Character string, either |
... |
Additional options passed to |
An object of class "pmsims" containing the estimated minimum sample
size and simulation diagnostics (inputs, fitted GP curve, intermediate
evaluations, and summary metrics).
Two formulations are supported.
Mean-based: find the smallest such that the expected model
performance exceeds the target , i.e.
Assurance-based: find the smallest such that the probability
the performance exceeds is at least (e.g. 0.80),
i.e.
Here, is the chosen performance metric and the probability/expectation
is over repeated samples of training data of size . The assurance
criterion explicitly accounts for variability across training sets; models
with higher variance typically require larger to satisfy it.
complexity selects the signal structure of the data-generating mechanism:
1 purely linear, 2 linear + quadratic, 3 linear + quadratic +
interaction, 4 the Friedman function. data_control is an optional list
fine-tuning the predictors:
nonlinear_strengthNumeric in [0, 1). Fraction of the signal
variance carried by the nonlinear, linearly-inaccessible component.
Applies to complexity 2 and 3 only; ignored (with a warning) for 1 and 4.
If omitted, the generator's per-complexity default is used.
correlationNumeric in . Pairwise correlation among the
candidate predictors. Default 0.3.
predictor_distributionOne of "normal", "uniform", "binary",
"exponential", "lognormal", "t", "laplace". "binary" selects
0/1 predictors and requires binary_predictor_prevalence; any other
value selects continuous predictors from that family. Default "normal".
binary_predictor_prevalenceNumeric in (0, 1). Prevalence of the
binary predictors; required when predictor_distribution = "binary",
ignored (with a warning) otherwise. Note: binary predictors are
incompatible with complexity 2/3 because squaring a 0/1 variable returns
itself.
simulate_binary(), simulate_survival(), simulate_custom()
set.seed(123) est <- simulate_continuous( signal_parameters = 3, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0), maximum_achievable_rsquared = 0.50, model = "lm", metric = "calibration_slope", target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 50, max_sample_size = 1000, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)set.seed(123) est <- simulate_continuous( signal_parameters = 3, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0), maximum_achievable_rsquared = 0.50, model = "lm", metric = "calibration_slope", target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 50, max_sample_size = 1000, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)
Compute the minimum sample size required to achieve a target level of
predictive performance using user-defined simulation components.
simulate_custom() is the low-level interface in pmsims: users supply a
data-generating function, a model-fitting function, and a metric function,
and the chosen search engine estimates the smallest meeting the
selected performance criterion.
simulate_custom( data_function, model_function, metric_function, target_performance, c_statistic = NULL, mean_or_assurance = "assurance", test_n = 30000, min_sample_size = NULL, max_sample_size = NULL, n_reps_total = 1000, n_reps_per = 20, method = "mlpwr", progress = TRUE, verbose = FALSE, ... )simulate_custom( data_function, model_function, metric_function, target_performance, c_statistic = NULL, mean_or_assurance = "assurance", test_n = 30000, min_sample_size = NULL, max_sample_size = NULL, n_reps_total = 1000, n_reps_per = 20, method = "mlpwr", progress = TRUE, verbose = FALSE, ... )
data_function |
Function taking a single argument, |
model_function |
Function that fits a model to the dataset returned by
|
metric_function |
Function that evaluates predictive performance on test
data. It must take three positional arguments in the order
|
target_performance |
Numeric target value for the chosen performance
metric. The search aims to find the smallest sample size |
c_statistic |
Optional numeric value used only by the internal
start-value heuristics for some outcome and metric combinations. In most
custom workflows this should be left as |
mean_or_assurance |
Character string specifying the criterion used to
define the minimum sample size. Must be either |
test_n |
Integer size of the test dataset used to evaluate model performance. This should usually be large enough that test-set variability is negligible relative to the training-sample search. |
min_sample_size |
Optional integer lower bound for the sample-size
search. If supplied, |
max_sample_size |
Optional integer upper bound for the sample-size
search. If supplied, |
n_reps_total |
Integer total number of simulation replications allocated
to the search. The search evaluates approximately
|
n_reps_per |
Integer number of simulation replications performed at each candidate sample size. |
method |
Character string specifying the search engine. Defaults to
|
progress |
Logical flag controlling whether the |
verbose |
Logical flag controlling engine-specific diagnostic output
when supported. For the bisection engine, setting |
... |
Additional arguments passed to the selected search engine. |
An object of class "pmsims" containing the estimated minimum sample size.
simulate_binary(), simulate_continuous(), simulate_survival()
# Three independent predictors with a population R-squared of 0.5. data_fun <- function(n) { x1 <- rnorm(n) x2 <- rnorm(n) x3 <- rnorm(n) y <- (x1 + x2 + x3) / sqrt(3) + rnorm(n) data.frame(y = y, x1 = x1, x2 = x2, x3 = x3) } model_fun <- function(dat) { stats::lm(y ~ ., data = dat) } # Calibration slope evaluated on independent test data. metric_fun <- function(test_data, fit, model) { preds <- stats::predict(fit, newdata = test_data) unname(stats::coef(stats::lm(test_data$y ~ preds))[2]) } attr(metric_fun, "metric") <- "calibration_slope" set.seed(123) est <- simulate_custom( data_function = data_fun, model_function = model_fun, metric_function = metric_fun, target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 25, max_sample_size = 1000, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)# Three independent predictors with a population R-squared of 0.5. data_fun <- function(n) { x1 <- rnorm(n) x2 <- rnorm(n) x3 <- rnorm(n) y <- (x1 + x2 + x3) / sqrt(3) + rnorm(n) data.frame(y = y, x1 = x1, x2 = x2, x3 = x3) } model_fun <- function(dat) { stats::lm(y ~ ., data = dat) } # Calibration slope evaluated on independent test data. metric_fun <- function(test_data, fit, model) { preds <- stats::predict(fit, newdata = test_data) unname(stats::coef(stats::lm(test_data$y ~ preds))[2]) } attr(metric_fun, "metric") <- "calibration_slope" set.seed(123) est <- simulate_custom( data_function = data_fun, model_function = model_fun, metric_function = metric_fun, target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 25, max_sample_size = 1000, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)
Compute the minimum sample size required to develop a prediction model with a
time-to-event (survival) outcome. As with the other wrappers, this uses a
simulation-based learning-curve approach with Gaussian-process surrogate
modelling to locate the smallest meeting the chosen performance
criterion.
simulate_survival( signal_parameters, noise_parameters = 0, complexity = 1, data_control = NULL, maximum_achievable_cindex, baseline_hazard = 1, censoring_rate, model = c("coxph", "lasso", "ridge", "rf", "xgboost"), metric = "calibration_slope", target_performance, n_reps_total = 1000, mean_or_assurance = "assurance", ... )simulate_survival( signal_parameters, noise_parameters = 0, complexity = 1, data_control = NULL, maximum_achievable_cindex, baseline_hazard = 1, censoring_rate, model = c("coxph", "lasso", "ridge", "rf", "xgboost"), metric = "calibration_slope", target_performance, n_reps_total = 1000, mean_or_assurance = "assurance", ... )
signal_parameters |
Integer. Number of candidate predictors associated with the outcome (i.e., true signal features). |
noise_parameters |
Integer. Number of candidate predictors not associated with the outcome (noise features). Default is 0. |
complexity |
Integer in 1:4 selecting the data-generating signal
structure (see Data control). Default |
data_control |
Optional named list controlling the predictors (see
Data control). Default |
maximum_achievable_cindex |
Numeric in (0, 1). Maximum achievable C-index with effectively unlimited data. This is used to calibrate the data-generating mechanism and is not the minimum acceptable threshold. |
baseline_hazard |
Numeric greater than 0. Baseline hazard level used by the data-generating mechanism (e.g., the constant hazard in an exponential baseline). Larger values imply shorter event times, all else equal. |
censoring_rate |
Numeric in [0, 1). Proportion of individuals expected
to be censored in the simulated datasets (administrative or random
censoring). Higher values imply fewer observed events for a fixed |
model |
Character string specifying the modelling algorithm. One of
|
metric |
Character string naming the performance metric used to assess
the sample size; defaults to
|
target_performance |
Numeric. Minimum acceptable value of the selected
performance metric |
n_reps_total |
Integer. Total number of simulation replications used by the engine across the search. |
mean_or_assurance |
Character string, either |
... |
Additional options passed to |
An object of class "pmsims" containing the estimated minimum sample
size and simulation diagnostics (inputs, fitted GP curve, intermediate
evaluations, and summary metrics).
Two formulations are supported.
Mean-based: find the smallest such that the expected model
performance exceeds the target , i.e.
Assurance-based: find the smallest such that the probability
the performance exceeds is at least (e.g. 0.80),
i.e.
Here, is the chosen performance metric and the probability/expectation
is over repeated samples of training data of size . The assurance
criterion explicitly accounts for variability across training sets; models
with higher variance typically require larger to satisfy it.
complexity selects the signal structure of the data-generating mechanism:
1 purely linear, 2 linear + quadratic, 3 linear + quadratic +
interaction, 4 the Friedman function. data_control is an optional list
fine-tuning the predictors:
nonlinear_strengthNumeric in [0, 1). Fraction of the signal
variance carried by the nonlinear, linearly-inaccessible component.
Applies to complexity 2 and 3 only; ignored (with a warning) for 1 and 4.
If omitted, the generator's per-complexity default is used.
correlationNumeric in . Pairwise correlation among the
candidate predictors. Default 0.3.
predictor_distributionOne of "normal", "uniform", "binary",
"exponential", "lognormal", "t", "laplace". "binary" selects
0/1 predictors and requires binary_predictor_prevalence; any other
value selects continuous predictors from that family. Default "normal".
binary_predictor_prevalenceNumeric in (0, 1). Prevalence of the
binary predictors; required when predictor_distribution = "binary",
ignored (with a warning) otherwise. Note: binary predictors are
incompatible with complexity 2/3 because squaring a 0/1 variable returns
itself.
simulate_binary(), simulate_continuous(), simulate_custom()
set.seed(123) est <- simulate_survival( signal_parameters = 1, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0), maximum_achievable_cindex = 0.70, baseline_hazard = 0.01, censoring_rate = 0.30, model = "coxph", metric = "calibration_slope", target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 25, max_sample_size = 500, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)set.seed(123) est <- simulate_survival( signal_parameters = 1, noise_parameters = 0, complexity = 1, data_control = list(correlation = 0), maximum_achievable_cindex = 0.70, baseline_hazard = 0.01, censoring_rate = 0.30, model = "coxph", metric = "calibration_slope", target_performance = 0.9, mean_or_assurance = "assurance", min_sample_size = 25, max_sample_size = 500, n_reps_total = 1000, test_n = 30000, progress = FALSE ) est est$min_n plot(est)