| Type: | Package |
| Title: | Leakage-Aware Discovery of Small Biomarker Panels |
| Version: | 0.1.3 |
| Author: | Fuhao Jiang [aut, cre] |
| Maintainer: | Fuhao Jiang <emr39515@gmail.com> |
| Description: | Discovers small binary-classification biomarker panels from count or expression matrices while prioritizing detectability, expression stability, and univariate discrimination. Candidate filtering and panel selection can be repeated inside nested cross-validation to reduce information leakage. The package provides shared resampling splits, exhaustive small-panel search, logistic model fitting with an automatic ridge fallback for unstable separation-prone fits, out-of-fold evaluation, selection-frequency summaries, and optional 'DESeq2' differential-expression support. The nested model-selection workflow follows Varma and Simon (2006) <doi:10.1186/1471-2105-7-91>, and the optional differential-expression analysis uses Love, Huber, and Anders (2014) <doi:10.1186/s13059-014-0550-8>. |
| License: | MIT + file LICENSE |
| URL: | https://github.com/Emr-27/detectPanel |
| BugReports: | https://github.com/Emr-27/detectPanel/issues |
| Encoding: | UTF-8 |
| Depends: | R (≥ 4.2.0) |
| Imports: | ggplot2, graphics, stats, utils |
| Suggests: | DESeq2, knitr, rmarkdown, pheatmap, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| Config/testthat/edition: | 3 |
| NeedsCompilation: | no |
| Packaged: | 2026-09-05 18:45:46 UTC; 24791 |
| Repository: | CRAN |
| Date/Publication: | 2026-09-15 11:00:41 UTC |
Leakage-Aware Discovery of Small Biomarker Panels
Description
Tools for detectability-aware candidate scoring, shared-split panel search, nested cross-validation, logistic model fitting with a separation-safe ridge fallback, prediction, plotting, and result export.
Details
The recommended entry point is discover_panel(). Candidate selection and
panel search are repeated inside every outer training split.
Author(s)
Fuhao Jiang <emr39515@gmail.com>
Compute Confusion-Matrix Metrics
Description
Computes fixed-threshold classification metrics without optimizing on the evaluation data.
Usage
confusion_metrics(response, probability, threshold = 0.5)
Arguments
response |
Non-missing binary 0/1 outcome. |
probability |
Predicted probability for class 1. |
threshold |
Probability classification threshold. |
Value
A one-row data frame with AUC, sensitivity, specificity, accuracy, balanced accuracy, and confusion counts.
Examples
response <- c(0, 0, 1, 1)
probability <- c(0.1, 0.4, 0.7, 0.9)
confusion_metrics(response, probability, threshold = 0.5)
Discover, Validate, and Refit a Biomarker Panel
Description
Runs nested validation and refits the most frequently selected exact panel on all samples.
Usage
detectPanel(...)
discover_panel(
counts, metadata, outcome, positive = NULL, sample_id = NULL,
exclude = character(), exclude_regex = NULL, panel_size = 3L,
candidate_n = 10L, transform_method = c("log2_cpm", "log2", "none"),
prior_count = 1, detection_threshold = 10,
detectability_weights = c(0.5, 0.35, 0.15),
preselection_weights = c(0.5, 0.5), min_mean = 100,
min_median = 20, min_detection = 0.8, min_group_detection = 0.5,
min_auc = 0.8, allow_fallback = FALSE, outer_v = 5L,
outer_repeats = 2L, inner_v = 5L, inner_repeats = 3L,
near_best_tolerance = 0.01, seed = 123L, max_combinations = 50000L
)
Arguments
counts |
Non-negative feature-by-sample matrix. |
metadata |
Sample metadata data frame. |
outcome |
Name of the binary outcome column. |
positive |
Positive-class label. |
sample_id |
Optional sample-ID column in metadata. |
exclude |
Exact feature names to remove. |
exclude_regex |
Optional feature-name regular expression to remove. |
panel_size |
Number of features per panel. |
candidate_n |
Maximum candidate-pool size inside each outer split. |
transform_method |
Modeling transformation. |
prior_count |
Offset used before log transformation. |
detection_threshold |
Count threshold defining detection. |
detectability_weights |
Weights for abundance, detection, and stability. |
preselection_weights |
Weights for detectability and individual AUC. |
min_mean, min_median, min_detection, min_group_detection, min_auc |
Training-only candidate filters. |
allow_fallback |
Whether to fill undersized candidate pools by rank. |
outer_v, outer_repeats |
Outer validation settings. |
inner_v, inner_repeats |
Inner panel-search settings. |
near_best_tolerance |
AUC tolerance before tie breaking. |
seed |
Random seed. |
max_combinations |
Maximum combinations per inner search. |
... |
Arguments passed from |
Value
A detectPanel_result object containing nested validation and a final fitted model.
Examples
set.seed(1)
y <- rep(c("Control", "Case"), each = 12)
x <- matrix(rpois(10 * 24, 50), 10,
dimnames = list(paste0("m", 1:10), paste0("s", 1:24)))
x[1:3, y == "Case"] <- x[1:3, y == "Case"] + 50
meta <- data.frame(group = y, row.names = colnames(x))
fit <- detectPanel(x, meta, "group", positive = "Case",
candidate_n = 6, min_mean = 5, min_median = 2,
min_detection = 0.2, min_group_detection = 0.1, min_auc = 0.55,
outer_v = 3, outer_repeats = 1, inner_v = 3, inner_repeats = 1)
fit$final_panel
Export detectPanel Results
Description
Writes nested metrics, predictions, selection frequencies, audits, and optional final-model files.
Usage
export_results(x, path, save_model = TRUE)
Arguments
x |
A |
path |
Output directory. |
save_model |
Whether to save an RDS object. |
Value
The normalized path, invisibly.
Examples
set.seed(6)
group <- rep(c("Control", "Case"), each = 10)
counts <- matrix(rpois(6 * 20, 40), 6,
dimnames = list(paste0("gene", 1:6), paste0("s", 1:20)))
counts[1:2, group == "Case"] <- counts[1:2, group == "Case"] + 35
metadata <- data.frame(group = group, row.names = colnames(counts))
fit <- discover_panel(counts, metadata, "group", positive = "Case",
panel_size = 2, candidate_n = 4, min_mean = 5, min_median = 5,
min_detection = 0.5, min_group_detection = 0.5, min_auc = 0.55,
outer_v = 2, outer_repeats = 1, inner_v = 2, inner_repeats = 1)
export_results(fit, file.path(tempdir(), "detectPanel-example"),
save_model = FALSE)
Fit and Predict with a Biomarker Panel
Description
Stores preprocessing and scaling parameters needed to predict new samples. Ordinary logistic regression is attempted first. If it is non-convergent or shows separation-like numerical behavior, an internal L2-penalized logistic fit is used as a deterministic fallback.
Usage
fit_panel(assay, outcome, features, positive = NULL,
transform_method = c("log2_cpm", "log2", "none"), prior_count = 1,
threshold_method = c("youden", "fixed"), fixed_threshold = 0.5)
## S3 method for class 'detectPanel_fit'
predict(object, newdata,
type = c("response", "class", "link"), ...)
Arguments
assay |
Feature-by-sample count or expression matrix. |
outcome |
Binary outcome aligned to columns. |
features |
Features included in the logistic model. |
positive |
Positive-class label. |
transform_method |
Input transformation. |
prior_count |
Log offset. |
threshold_method |
Training threshold method. |
fixed_threshold |
Fixed probability threshold. |
object |
A fitted |
newdata |
Feature-by-new-sample matrix. |
type |
Prediction scale. |
... |
Additional arguments, currently unused. |
Value
A fitted model object or prediction vector. The fitted object records
fit_method and ridge_lambda when the internal ridge fallback is used.
Examples
set.seed(4)
outcome <- rep(c("Control", "Case"), each = 8)
assay <- matrix(rpois(4 * 16, 40), 4,
dimnames = list(paste0("gene", 1:4), paste0("s", 1:16)))
assay[1:2, outcome == "Case"] <- assay[1:2, outcome == "Case"] + 25
fit <- fit_panel(assay, outcome, c("gene1", "gene2"), positive = "Case")
predict(fit, assay[, 1:3], type = "response")
Flag Potential PCA Outliers Without Deleting Samples
Description
Provides non-destructive PCA quality-control flags for review alongside independent QC evidence.
Usage
flag_pca_outliers(expression, group = NULL, n_components = 5L,
threshold = NULL)
Arguments
expression |
Transformed feature-by-sample matrix. |
group |
Optional sample group vector. |
n_components |
Maximum number of principal components used. |
threshold |
Optional positive robust-distance threshold. |
Value
A sample-level data frame with PCA coordinates, robust distance, and a flag.
Examples
set.seed(7)
expression <- matrix(rnorm(5 * 10), 5,
dimnames = list(paste0("gene", 1:5), paste0("s", 1:10)))
flag_pca_outliers(expression, group = rep(c("Control", "Case"), each = 5),
n_components = 2, threshold = 4)
Shared Resampling Splits and Exhaustive Panel Search
Description
All panels are evaluated on the same folds to permit fair paired comparison. A repeated-CV AUC is retained only when every sample in that repeat receives a finite out-of-fold prediction; failed folds are not silently dropped.
Usage
make_repeated_stratified_splits(outcome, v = 5L, repeats = 10L, seed = 123L)
search_panels(expression, outcome, candidates, panel_size = 3L,
splits = NULL, candidate_table = NULL, v = 5L, repeats = 10L,
seed = 123L, near_best_tolerance = 0.01, max_combinations = 50000L)
Arguments
outcome |
Binary vector. |
v |
Number of folds. |
repeats |
Number of repeated fold assignments. |
seed |
Random seed. |
expression |
Transformed feature-by-sample matrix. |
candidates |
Candidate feature names. |
panel_size |
Number of features per panel. |
splits |
Reusable splits from |
candidate_table |
Optional candidate scoring table. |
near_best_tolerance |
AUC tolerance before expression-aware tie breaking. |
max_combinations |
Safety limit for exhaustive search. |
Value
A list of splits or a detectPanel_search object.
Examples
set.seed(3)
outcome <- rep(0:1, each = 8)
expression <- matrix(rnorm(4 * 16), 4,
dimnames = list(paste0("gene", 1:4), paste0("s", 1:16)))
expression[1:2, outcome == 1] <- expression[1:2, outcome == 1] + 1.5
splits <- make_repeated_stratified_splits(outcome, v = 2, repeats = 1)
search_panels(expression, outcome, rownames(expression), panel_size = 2,
splits = splits)
Nested Validation of Biomarker Panel Discovery
Description
Repeats candidate selection and panel search within every outer training split. If every outer split fails, the error message includes each split identifier and its original stage-specific failure message.
Usage
nested_validate_panels(counts, metadata, outcome, positive = NULL,
sample_id = NULL, exclude = character(), exclude_regex = NULL,
panel_size = 3L, candidate_n = 10L,
transform_method = c("log2_cpm", "log2", "none"), prior_count = 1,
detection_threshold = 10, detectability_weights = c(0.5, 0.35, 0.15),
preselection_weights = c(0.5, 0.5), min_mean = 100,
min_median = 20, min_detection = 0.8, min_group_detection = 0.5,
min_auc = 0.8, allow_fallback = FALSE, outer_v = 5L,
outer_repeats = 2L, inner_v = 5L, inner_repeats = 3L,
near_best_tolerance = 0.01, seed = 123L, max_combinations = 50000L)
Arguments
counts, metadata, outcome, positive, sample_id, exclude, exclude_regex |
See |
panel_size, candidate_n, transform_method, prior_count |
Model and search settings. |
detection_threshold, detectability_weights, preselection_weights |
Marker-scoring settings. |
min_mean, min_median, min_detection, min_group_detection, min_auc |
Training-only candidate filters. |
allow_fallback |
Whether to fill undersized candidate pools by rank. |
outer_v, outer_repeats, inner_v, inner_repeats |
Nested resampling settings. |
near_best_tolerance, seed, max_combinations |
Search controls. |
Value
A detectPanel_nested object with predictions, metrics, selection frequencies, audits, and errors.
Examples
set.seed(5)
group <- rep(c("Control", "Case"), each = 10)
counts <- matrix(rpois(6 * 20, 40), 6,
dimnames = list(paste0("gene", 1:6), paste0("s", 1:20)))
counts[1:2, group == "Case"] <- counts[1:2, group == "Case"] + 35
metadata <- data.frame(group = group, row.names = colnames(counts))
nested_validate_panels(counts, metadata, "group", positive = "Case",
panel_size = 2, candidate_n = 4, min_mean = 5, min_median = 5,
min_detection = 0.5, min_group_detection = 0.5, min_auc = 0.55,
outer_v = 2, outer_repeats = 1, inner_v = 2, inner_repeats = 1)
Diagnostic Plotting Helpers
Description
Creates expression, ROC, volcano, and optional heatmap views from package results.
Usage
plot_marker_expression(expression, metadata, features, outcome,
sample_id = NULL)
plot_marker_roc(expression, outcome, features, positive = NULL)
plot_volcano(results, feature_col = "feature",
lfc_col = "log2FoldChange", padj_col = "padj",
padj_cut = 0.05, lfc_cut = 1)
plot_panel_heatmap(expression, features, metadata = NULL,
annotation_cols = NULL, scale = "row", ...)
Arguments
expression |
Transformed feature-by-sample matrix. |
metadata |
Sample metadata aligned by row names. |
features |
Features to display. |
outcome |
Outcome column name or a binary vector, depending on function. |
sample_id |
Optional metadata sample-ID column. |
positive |
Positive-class label. |
results |
Differential-expression result data frame. |
feature_col, lfc_col, padj_col |
Differential-expression column names. |
padj_cut, lfc_cut |
Volcano significance cutoffs. |
annotation_cols |
Metadata columns used as heatmap annotations. |
scale |
Heatmap scaling option. |
... |
Additional arguments passed to |
Value
A ggplot object or the object returned by pheatmap::pheatmap().
Examples
set.seed(8)
group <- rep(c("Control", "Case"), each = 5)
expression <- matrix(rnorm(3 * 10), 3,
dimnames = list(paste0("gene", 1:3), paste0("s", 1:10)))
expression[1, group == "Case"] <- expression[1, group == "Case"] + 2
metadata <- data.frame(group = group, row.names = colnames(expression))
plot_marker_expression(expression, metadata, "gene1", "group")
plot_marker_roc(expression, group, "gene1", positive = "Case")
de_results <- data.frame(feature = rownames(expression),
log2FoldChange = c(2, -1.5, 0.2), padj = c(0.01, 0.03, 0.8))
plot_volcano(de_results)
if (requireNamespace("pheatmap", quietly = TRUE)) {
plot_panel_heatmap(expression, c("gene1", "gene2"), metadata,
annotation_cols = "group", silent = TRUE)
}
Predict samples using a fitted detectPanel result
Description
Applies the final fitted panel model from a detectPanel_result
object to new count data.
Usage
## S3 method for class 'detectPanel_result'
predict(object, newdata,
type = c("response", "class"), ...)
Arguments
object |
A |
newdata |
A feature-by-sample numeric matrix. |
type |
Prediction output type: probabilities or classes. |
... |
Additional arguments. |
Value
A vector of predicted probabilities or classes.
Examples
set.seed(9)
group <- rep(c("Control", "Case"), each = 10)
counts <- matrix(rpois(6 * 20, 40), 6,
dimnames = list(paste0("gene", 1:6), paste0("s", 1:20)))
counts[1:2, group == "Case"] <- counts[1:2, group == "Case"] + 35
metadata <- data.frame(group = group, row.names = colnames(counts))
fit <- discover_panel(counts, metadata, "group", positive = "Case",
panel_size = 2, candidate_n = 4, min_mean = 5, min_median = 5,
min_detection = 0.5, min_group_detection = 0.5, min_auc = 0.55,
outer_v = 2, outer_repeats = 1, inner_v = 2, inner_repeats = 1)
predict(fit, counts[, 1:3], type = "response")
Optional DESeq2 Differential-Expression Adapter
Description
Produces differential-expression evidence separately from predictive validation.
Usage
run_deseq2(counts, metadata, design, contrast, min_total_count = 10, ...)
Arguments
counts |
Feature-by-sample count matrix. |
metadata |
Sample metadata aligned by row names. |
design |
Design formula. |
contrast |
Contrast passed to |
min_total_count |
Minimum total feature count. |
... |
Additional arguments passed to |
Value
A list containing a fitted DESeq2 object and result data frame.
Examples
if (requireNamespace("DESeq2", quietly = TRUE)) {
set.seed(10)
ngenes <- 300L
nsamples <- 8L
base_mean <- exp(seq(log(10), log(1000), length.out = ngenes))
counts <- matrix(
rnbinom(ngenes * nsamples, mu = rep(base_mean, nsamples), size = 10),
nrow = ngenes,
dimnames = list(paste0("gene", seq_len(ngenes)),
paste0("s", seq_len(nsamples))))
metadata <- data.frame(
condition = factor(rep(c("Control", "Case"), each = nsamples / 2),
levels = c("Control", "Case")),
row.names = colnames(counts))
result <- run_deseq2(counts, metadata, ~ condition,
c("condition", "Case", "Control"))
head(result$results)
}
Score and Preselect Candidate Biomarkers
Description
Combines abundance, detection, robust stability, and fixed-direction rank AUC.
Usage
score_detectability(counts, group = NULL, detection_threshold = 10,
weights = c(abundance = 0.5, detection = 0.35, stability = 0.15),
stability_transform = c("log2", "none"))
evaluate_markers(expression, outcome, positive = NULL)
preselect_markers(detectability, marker_metrics, min_mean = 100,
min_median = 20, min_detection = 0.8, min_group_detection = 0.5,
min_auc = 0.8, weights = c(detectability = 0.5, auc = 0.5),
fallback_n = 0L)
Arguments
counts |
Non-negative feature-by-sample count matrix. |
group |
Optional grouping vector. |
detection_threshold |
Value defining detection. |
weights |
Non-negative weights summing to one. |
stability_transform |
Transformation before robust dispersion scoring. |
expression |
Transformed feature-by-sample matrix. |
outcome |
Binary outcome aligned to columns. |
positive |
Positive-class label. |
detectability |
Output from |
marker_metrics |
Output from |
min_mean, min_median, min_detection, min_group_detection, min_auc |
Candidate filters. |
fallback_n |
Optional number of ranked markers returned when hard filters are insufficient. |
Value
A feature-level data frame.
Examples
set.seed(2)
group <- rep(c("Control", "Case"), each = 6)
counts <- matrix(rpois(5 * 12, 30), 5,
dimnames = list(paste0("gene", 1:5), paste0("s", 1:12)))
counts[1:2, group == "Case"] <- counts[1:2, group == "Case"] + 30
detectability <- score_detectability(counts, group, detection_threshold = 5)
marker_metrics <- evaluate_markers(log2(counts + 1), group, positive = "Case")
preselect_markers(detectability, marker_metrics, min_mean = 5,
min_median = 5, min_detection = 0.5, min_group_detection = 0.5,
min_auc = 0.6)
Validate, Filter, and Transform Assay Data
Description
Core utilities for reproducible assay preparation without hard-coded dataset rules.
Usage
validate_assay_data(assay, metadata, outcome, positive = NULL, sample_id = NULL)
exclude_features(assay, features = character(), regex = NULL, ignore_case = TRUE)
transform_assay(assay, method = c("log2_cpm", "log2", "none"), prior_count = 1)
Arguments
assay |
Numeric feature-by-sample matrix. |
metadata |
Sample metadata. |
outcome |
Binary outcome column name. |
positive |
Positive-class label. |
sample_id |
Optional metadata sample-ID column. |
features |
Exact feature names to remove. |
regex |
Optional feature-name regular expression. |
ignore_case |
Whether regular-expression matching ignores case. |
method |
Transformation method. |
prior_count |
Positive log offset. |
Value
Aligned data, a filtering audit, or a transformed matrix, depending on the function.
Examples
assay <- matrix(1:24, nrow = 3,
dimnames = list(c("gene1", "gene2", "control_gene"), paste0("s", 1:8)))
metadata <- data.frame(group = rep(c("Control", "Case"), each = 4),
row.names = colnames(assay))
validated <- validate_assay_data(assay, metadata, "group", positive = "Case")
filtered <- exclude_features(validated$assay, regex = "control")
transform_assay(filtered$assay, method = "log2")