Wrapper around rmoo::nsga3() (or rmoo::nsga2()) that composes registered metric functions into a multi-objective search for compact biomarker panels. Candidate solutions are represented as weights over a feature pool; features with weight > 0.5 are included in the panel (up to max_features). This threshold-based selection allows variable panel sizes, making the num_features objective meaningful. Inputs may be a single cohort (matrix, data.frame, or SummarizedExperiment) or multiple cohorts supplied as lists of such objects. Optimisation should be run on training data only—use evaluate_panel() for held-out validation.

optimize_panel(
  x,
  y,
  objectives = define_objectives(metrics = c("sensitivity", "specificity",
    "num_features")),
  max_features = 5L,
  feature_pool = NULL,
  feature_transform = "pairwise_ratios",
  feature_alignment = c("intersection", "majority", "impute_median"),
  constraints = list(),
  algorithm = c("NSGA-III", "NSGA-II"),
  nsga_control = list(),
  assay = NULL,
  seed = 42L,
  fitness_mode = c("cv", "in_sample", "within_cohort_val", "within_cohort_rotating",
    "loco"),
  fitness_cv_folds = 5L,
  n_val_splits = 10L,
  train_ratio = 1,
  val_ratio = 0,
  np_alpha = 0.15,
  np_delta = 0.05,
  regularized = TRUE,
  regularized_alpha = 0.5,
  selection_threshold = "adaptive",
  record_history = FALSE,
  fitness_cv = NULL
)

Arguments

x

Matrix-like object, SummarizedExperiment, or list of matrices / experiments representing one or more cohorts.

y

Binary response (factor, character, or logical) aligned with x. When x is a list, y must be a list of the same length.

objectives

Named list of objective descriptors as returned by define_objectives().

max_features

Maximum number of biomarkers permitted in a panel. Acts as an upper bound; actual panel size varies based on how many features have weight > 0.5 in the decision vector. Minimum is 2 features when regularized = TRUE (glmnet requirement), or 1 when regularized = FALSE. NULL removes the cap (panels may use the whole feature_pool).

feature_pool

Optional subset of base feature identifiers (names or integer indices) to consider during optimization. These are the original feature names before any transformation. When using pairwise transforms, transformed labels like "A--B" are also accepted and will be mapped back to their constituent base features. Each cohort is subset to these columns; no further selection is applied. Defaults to NULL, which searches every shared feature and warns that this can be slow.

feature_transform

Transformation applied to selected features after base feature selection. The NSGA algorithm selects base features, then this transform is applied on-the-fly. Defaults to "pairwise_ratios", which generates pairwise contrasts via pairwise_col_diff() to dampen batch effects. Other options include "pairwise_log_ratios" (log-ratios for compositional data), "reference_norm" (normalize relative to a reference feature), and "none" (no transformation). Use register_feature_transform() to add custom strategies. See feature_transform_registry() for available options.

feature_alignment

Strategy for aligning features across cohorts:

"intersection"

(Default) Keep only features present in ALL cohorts. Features missing in any cohort are dropped.

"majority"

Keep features present in >= 50% of cohorts. Missing values are imputed with cohort medians.

"impute_median"

Keep all features from any cohort. Missing values are imputed with cohort medians.

constraints

Optional list of constraint descriptors (e.g., from min_metric_constraint()) that must evaluate to TRUE for a candidate solution to be considered feasible.

algorithm

Multi-objective optimization algorithm. "NSGA-III" (default) uses reference-point-based selection and gives better Pareto front diversity for many-objective problems (3+ objectives). "NSGA-II" uses crowding distance for diversity preservation and is typically adequate for 2 objectives.

nsga_control

Named list of arguments passed to rmoo::nsga3() (or rmoo::nsga2()). For NSGA-III, n_partitions controls reference point density (default computed from number of objectives). Defaults to adaptive values based on feature pool size. Note: parallel = TRUE is not recommended as it is slower than sequential execution due to overhead.

assay

For SummarizedExperiment inputs, assay name or index to use.

seed

Integer seed for reproducibility (default 42L). The seed is set once before partitioning, so both the data split and the NSGA search are reproducible. It is set on the global RNG and is not restored on exit, so a resampling loop around optimize_panel() must pass its own seed per iteration. Pass NULL to leave the RNG untouched (results then vary between runs).

fitness_mode

How candidate panels are scored during the NSGA search:

"cv"

(Default) k-fold cross-validation within the training partition; objectives are computed on out-of-fold predictions.

"in_sample"

Train and score on the same samples. Fast but overfits; kept for small datasets and backward compatibility.

"within_cohort_val"

Train on the training partition, score on the validation partition. Requires val_ratio > 0.

"within_cohort_rotating"

Pool train and validation, pre-compute n_val_splits stratified splits, and rotate to the next split every generation so no single split can be overfit to. Requires val_ratio > 0.

"loco"

Leave-one-cohort-out: score each candidate on concatenated out-of-cohort predictions, making cross-cohort transferability the objective. Requires at least 2 cohorts.

fitness_cv_folds

Number of cross-validation folds for fitness evaluation when fitness_mode = "cv". Default is 5.

n_val_splits

Number of rotating train/validation splits to pre-compute when fitness_mode = "within_cohort_rotating" (default 10).

train_ratio, val_ratio

Per-cohort shares used for stratified partitioning; the remainder (1 - train_ratio - val_ratio) is held out and stored on the result for calibrate_panel(). The default train_ratio = 1, val_ratio = 0 means no partitioning at all: the whole input is the training data.

np_alpha

Type I error rate for Neyman-Pearson threshold selection. Stored on the result for calibrate_panel() when a held-out partition exists (default 0.15).

np_delta

Tolerance for Neyman-Pearson threshold selection, stored alongside np_alpha (default 0.05).

regularized

Logical; if TRUE (default), use regularized regression (elastic net via glmnet) for scoring candidates during optimization. This typically produces more stable fitness estimates and better out-of-sample performance compared to unregularized logistic regression.

regularized_alpha

Elastic net mixing parameter when regularized = TRUE. alpha = 1 is lasso, alpha = 0 is ridge, and values in between give elastic net. Default is 0.5 (elastic net).

selection_threshold

Either a fixed numeric threshold in (0,1) for feature selection, or "adaptive" (default) to use gap-based selection that allows variable panel sizes in the Pareto front. Fixed thresholds (e.g., 0.5) tend to produce panels at max_features because more features improve accuracy. Adaptive selection finds natural breakpoints in the weight distribution, enabling the num_features objective to drive panel size diversity.

record_history

Logical; if TRUE, capture the population, fitness, and NSGA rank at every generation and attach the result to the returned OptimizationResult. Retrieve via nsga_history(). Default FALSE. Adds modest overhead (one matrix copy per generation) and is intended for diagnostic / visualization use, e.g. plotting how the Pareto front evolves over maxiter.

fitness_cv

Deprecated. Logical alias for fitness_mode: TRUE maps to "cv" and FALSE to "in_sample". Supplying it emits a deprecation warning and overrides fitness_mode.

Value

An OptimizationResult containing the Pareto-optimal solutions. Use summarize_solutions() to inspect solutions and fit_panel() to fit a model on a selected solution.

Partitioning

When train_ratio + val_ratio < 1, each cohort is stratified into train / validation / held-out shares before the search runs; fitness_mode determines which of these partitions the NSGA search actually scores against (see above). The returned OptimizationResult always carries the pooled train + validation data as aggregated_x/aggregated_y/aggregated_cohort. The held-out partition, np_alpha, np_delta, and partition_info are stored on control only when a positive held-out share exists (train_ratio + val_ratio < 1); with the default train_ratio = 1, val_ratio = 0 no partitioning happens at all and those fields are absent. calibrate_panel() and evaluate_pareto_solutions() read control$heldout_x / control$heldout_y / control$heldout_cohort when present.