The outstandR package
uses R’s S3 object-oriented system to manage different statistical
approaches (strategies) for population adjustment. This modular design
makes it straightforward to implement new methods without rewriting the
package’s core infrastructure.
To add a new statistical method (e.g., custom_method),
you need to complete the following 5 steps:
File: R/strategy_.R
First, create a user-facing constructor function that validates user
inputs and instantiates the strategy object. By convention, this
function should be named
strategy_<method_name>().
It must call the internal new_strategy() function,
passing the class name and the validated arguments.
#' @rdname strategy
#'
#' @section Custom Method:
#' Describe the statistical theory behind the new method here.
#'
#' @param trt_var Treatment variable name
#' @param my_custom_arg Description of your custom argument
#'
#' @return `custom_method` class object
#' @export
strategy_custom_method <- function(formula = NULL,
family = gaussian(link = "identity"),
trt_var = NULL,
my_custom_arg = 100) {
# 1. Validate inputs using built-in helpers
check_formula(formula, trt_var)
check_family(family)
if (my_custom_arg <= 0) {
stop("my_custom_arg must be greater than 0.")
}
# 2. Bundle arguments
args <- list(formula = formula,
family = family,
trt_var = get_treatment_name(formula, trt_var),
my_custom_arg = my_custom_arg)
# 3. Create object
do.call(new_strategy, c(strategy = "custom_method", args))
}File: R/strategy-class.R
Update the strategy-class documentation to inform users
about the internal fields your new subclass carries. Add a new
\item block under the details section:
File: Create a new file (e.g.,
R/custom_method.R) or add to existing calculation
files.
Write the function that actually performs the maths. It will receive
two arguments: strategy (the object you created in Step 1)
and analysis_params (a list containing the pre-processed
ipd, ald, treatment labels, etc.).
Crucial Requirement: Your function MUST return a
list containing exactly two elements: means and
model.
means$A: Estimates for the comparator treatment group
(can be a scalar, or a vector of bootstrap/MCMC draws).means$C: Estimates for the reference treatment group
(can be a scalar, or a vector of bootstrap/MCMC draws).model: A list of any underlying models or diagnostics
(e.g., weights, glm fit objects) you want returned to the user.#' Calculate Custom Method
#' @keywords internal
calc_custom_method <- function(strategy, analysis_params) {
# Extract data and parameters
ipd <- analysis_params$ipd
ald <- analysis_params$ald
formula <- strategy$formula
custom_val <- strategy$my_custom_arg
# ... [PERFORM YOUR STATISTICAL MODELLING HERE] ...
# e.g., fit a model, run bootstraps, calculate weights
# Construct the strict return object
list(
means = list(
A = estimated_means_for_A,
C = estimated_means_for_C
),
model = list(
fit = my_fitted_model,
custom_val = custom_val
)
)
}File: R/calc_IPD_stats.R
To plug your core function into outstandR’s top-level
execution, you must register it as an S3 method for
calc_IPD_stats.
Because outstandR handles the relative treatment effect
transformations (ATE) and variance estimation automatically, you simply
pass your core function to the IPD_stat_factory().
File: R/print-strategies.R
Finally, create a nice console printout for when a user types the
name of their instantiated strategy. Extend the generic
print.strategy using NextMethod() to print the
common fields, then print your specific parameters.
If your new method utilizes a random number generator (e.g., MCMC
sampling, pseudo-population simulation), you might need to catch the
top-level seed argument.
File: R/outstandR.R
Create an add_seed S3 method for your strategy:
#' @export
add_seed.custom_method <- function(strategy, fn_args, seed) {
if (!is.null(seed)) {
fn_args$seed <- seed
}
return(fn_args)
}Here is a section you can append to your vignette or developer guide. It provides a copy-pasteable utility script that future contributors can use to automate the boilerplate setup for a new strategy.
If you or your contributors find yourselves adding new strategies
frequently, you can automate the creation of the boilerplate files using
the usethis package.
We recommend saving the following helper function in a script file
(e.g., scripts/scaffold_strategy.R). This script will
automatically create the necessary calculation file and open the
registry files for you to edit.
When you are ready to build a new method, simply source the script and run the function with the name of your new method:
# Load the helper function
source("scripts/scaffold_strategy.R")
# Scaffold a new method called "gcomp_rf" (e.g., Random Forest G-computation)
scaffold_strategy("gcomp_rf")This will instantly set up your workspace, leaving you to focus solely on the statistical implementation rather than package wiring!
Here is a section you can add to your Markdown document (perhaps at
the end, right before or after the scaffolding script) that outlines
best practices for writing a robust, user-friendly strategy in
outstandR.
When contributing a new statistical approach to
outstandR, following these guidelines ensures your method
is robust, user-friendly, and maintainable.
Catch user errors in the constructor function
(strategy_custom_method()) before any heavy computation
begins.
check_formula() and
check_family() helpers.model ObjectThe core calculation function must return a list with
means and model. While means is
strictly structured, the model element is a free-form list.
Use this to give the user as much diagnostic power as possible.
glm or stanfit), calculated
weights, convergence status flags, and effective sample sizes
(ESS).model from the final
outstandR object and check diagnostics themselves.Many population adjustment methods rely on optimization or iterative fitting (like GLMs or MAIC). If an internal model fails to converge, your function shouldn’t crash the entire R session—especially during a bootstrap loop.
tryCatch().NA for the means and pass a
warning to the user.fit <- tryCatch({
glm(formula, data = ipd, family = family)
}, warning = function(w) {
# Handle warnings (e.g., probabilities numerically 0 or 1)
glm(formula, data = ipd, family = family)
}, error = function(e) {
NULL # Return NULL on hard failure
})
if (is.null(fit)) return(list(means = list(A = NA, C = NA), model = list(converged = FALSE)))verbose FlagoutstandR provides a global verbose
argument so users can silence console output during heavy
simulations.
verbose <- isTRUE(analysis_params$verbose)
at the top of your calculation function.cli package to print progress updates or warn
users about computationally expensive steps, but only if
verbose is true.Whenever you add a new strategy, add a corresponding test file in
tests/testthat/ (e.g.,
test-custom_method.R).
AC_IPD_binY_contX, etc.) to run a fast, low-iteration
version of your method to ensure the output list matches the required
list(means = ..., model = ...) structure.