vignettes/getting-started.Rmd
getting-started.RmdMOBID (Multi-Objective Biomarker Identification and
Discovery) helps researchers build compact biomarker signatures when
multiple cohorts are available and the number of measured features
greatly exceeds the number of samples. The package delegates the heavy
lift of multi-objective search to rmoo (NSGA-II/NSGA-III)
while exposing a friendly interface for selecting metrics, restricting
the feature pool, and summarising the resulting Pareto front.
This vignette walks through three steps:
optimize_panel() to discover candidate panels and
inspect the results.For demonstration we simulate four cohorts with
simulate_expression_data(). The metadata
element records which genes carry the planted signal.
cohorts <- simulate_expression_data(p = 250, n = 100, k = 4, seed = 20240220)
lapply(cohorts$x_list, dim)
#> $x1
#> [1] 100 250
#>
#> $x2
#> [1] 100 250
#>
#> $x3
#> [1] 100 250
#>
#> $x4
#> [1] 100 250
colnames(cohorts$x_list[[1]])[1:6]
#> [1] "gene_0001" "gene_0002" "gene_0003" "gene_0004" "gene_0005" "gene_0006"Each element of x_list is a log-scale expression matrix
with the same feature ordering across cohorts. The matching
y_list contains binary outcome labels stored as factors
with levels "No" and "Yes".
Metrics are registered in an extensible registry. You can inspect the
available metrics with metric_registry().
names(metric_registry())
#> [1] "auc" "balanced_accuracy"
#> [3] "cohort_auc_gap" "cohort_auc_var"
#> [5] "cohort_leakage" "f1"
#> [7] "max_cohort_brier" "min_cohort_auc"
#> [9] "npv" "num_features"
#> [11] "pauc" "precision"
#> [13] "sensitivity" "sensitivity_at_specificity"
#> [15] "specificity" "specificity_at_sensitivity"To emphasise clinical utility we combine sensitivity, specificity, and a panel size penalty. We also tighten the classification probability cutoff for sensitivity.
objectives <- define_objectives(
metrics = c("sensitivity", "specificity", "num_features"),
params = list(sensitivity = list(cutoff_prob = 0.6))
)
objectives[["sensitivity"]]$direction
#> [1] "maximize"When multiple cohorts are available you can layer on transfer-oriented objectives that operate on cohort-level sensitivities, calibration, and feature shifts. These metrics automatically receive cohort labels during optimisation.
transfer_objectives <- define_objectives(
metrics = c(
"min_cohort_auc",
"cohort_auc_gap",
"cohort_auc_var",
"max_cohort_brier"
)
)Rule-out diagnostics pin the operating point at high sensitivity and
then push specificity as far as it will go. There is no dedicated helper
for this — it is an ordinary define_objectives() call in
which specificity_at_sensitivity’s
target_sensitivity and pauc’s
sens_floor both name the same operating point.
ruleout_objectives <- define_objectives(
metrics = c("specificity_at_sensitivity", "pauc", "min_cohort_auc",
"num_features"),
params = list(
specificity_at_sensitivity = list(target_sensitivity = 0.90),
pauc = list(sens_floor = 0.90, partial_auc_correct = TRUE)
)
)No hard constraint is needed: because both metrics are already
evaluated at the 0.90 sensitivity floor, candidates that cannot reach it
score poorly on their own. Add a
min_metric_constraint("sensitivity", threshold = 0.9) only
if you want infeasible candidates discarded outright rather than ranked
last.
Some clinical settings demand a hard floor on performance before
optimising another objective. Supply constraint descriptors—such as
min_metric_constraint()—via the constraints
argument to optimize_panel(). The optimiser will discard
any candidate failing the constraint.
constraints <- list(
min_metric_constraint("sensitivity", threshold = 0.9,
params = list(cutoff_prob = 0.6))
)optimize_panel() accepts single matrices or lists of
cohorts. We restrict the feature pool to keep the search lightweight for
this vignette.
set.seed(9001)
result <- optimize_panel(
x = cohorts$x_list,
y = cohorts$y_list,
objectives = objectives,
max_features = 5,
feature_pool = colnames(cohorts$x_list[[1]])[1:50],
nsga_control = list(popsize = 64, generations = 60)
)The result is a BiomarkerPanelResult object that stores
the selected features, objective scores, and metadata about the
optimisation run.
slotNames(result)
result@features
result@metrics
head(result@objectives)The multi-cohort call above scores every candidate by 5-fold
cross-validation within the training data by default. Pass
fitness_mode together with train_ratio (and
val_ratio) to carve off a stratified partition per cohort
and score candidates against it instead:
fitness_mode = "loco" scores each candidate on left-out
cohorts, "within_cohort_val" and
"within_cohort_rotating" score on a held-back validation
share, and "cv" (the overall default) k-fold
cross-validates within the training partition. Whenever
train_ratio + val_ratio < 1, the remaining share is
stored on result@control$heldout_x / heldout_y
/ heldout_cohort for calibrate_panel() to use
afterwards.
We can visualise Pareto-efficient solutions with
plot_pareto_front(). Pick any pair of objectives and
extract the corresponding columns.
library(ggplot2)
pareto_df <- subset(result@objectives, objective %in% c("sensitivity", "specificity"))
pareto_df <- reshape(
pareto_df,
idvar = "solution_id",
timevar = "objective",
direction = "wide"
)
plot_pareto_front(
data.frame(
objective_x = pareto_df$value.sensitivity,
objective_y = pareto_df$value.specificity,
label = paste0("Solution ", pareto_df$solution_id)
),
xlab = "Sensitivity",
ylab = "Specificity"
)register_metric().BiocCheck::BiocCheck() and R CMD check
before submitting to Bioconductor.