CorePAM: A 24-Gene PAM50-Derived Expression Score with Cross-Platform External Validation for Breast Cancer Prognosis
PhD thesis project — Rafael de Negreiros Botan
Reducing the 50-gene PAM50 panel to the smallest prognostic subset (CorePAM, 24 genes) whose out-of-fold C-index is non-inferior (delta-C <= 0.010) to the full 50-gene elastic-net Cox model. Validated across four independent cohorts spanning RNA-seq and microarray platforms.
PAM50 is the gold-standard 50-gene expression panel for breast cancer molecular classification. CorePAM asks: how few of those 50 genes are actually needed for prognostic scoring?
Using elastic-net Cox regression on the SCAN-B cohort (n = 3,069), we derived a 24-gene subset that retains non-inferior prognostic discrimination, then validated it on four independent cohorts (TCGA-BRCA, METABRIC, GSE20685, GSE1456) covering both RNA-seq and microarray platforms. A secondary analysis evaluated the CorePAM score as a predictor of pathological complete response (pCR) to neoadjuvant chemotherapy across five additional cohorts.
Key results:
- Random-effects meta-analysis HR = 1.37 (95% CI 1.24-1.52; I2 = 38.2%; p = 1.6 x 10^-9) per 1-SD increase
- Concordance (C-index) 0.62-0.66 across validation cohorts
- pCR meta-analysis OR = 1.69 (95% CI 1.39-2.05; I2 = 0%)
| Cohort | Role | Platform | Endpoint | N | Events | Median FU |
|---|---|---|---|---|---|---|
| SCAN-B (GSE96058) | Training | RNA-seq | OS | 3,069 | 322 (10.5%) | 54.9 mo |
| TCGA-BRCA | Validation | RNA-seq | OS | 1,072 | 150 (14.0%) | 32.0 mo |
| METABRIC | Validation | Microarray (Illumina) | DSS | 1,978 | 646 (32.7%) | 159.0 mo |
| GSE20685 | Validation | Microarray (Affymetrix) | OS | 327 | 83 (25.4%) | 112.8 mo |
| GSE1456 | Validation | Microarray (Affymetrix) | OS | 159 | 40 (25.2%) | 91.0 mo |
| Cohort | Role | Platform | N (analyzed) | pCR rate |
|---|---|---|---|---|
| GSE25066 | Primary | HGU133A | 182 | 23.1% |
| GSE20194 | Primary | HGU133Plus2 | 278 | 20.1% |
| GSE32646 | Primary | HGU133Plus2 | 115 | 23.5% |
| I-SPY 1 (GSE22226) | Primary | Agilent 44K | 122 | 26.2% |
| I-SPY 2 (GSE194040) | Exploratory | Agilent 44K | 986 | 32.4% |
Note: pCR sample sizes reflect post-QC cohorts (complete CorePAM score + endpoint + covariates).
.
├── scripts/ R scripts (numbered 00-36)
│ ├── 00_setup.R Environment + frozen parameters (sourced by all)
│ ├── 00_colors.R Colour palette for all figures
│ ├── 01-04_*.R Data download, clinical harmonisation, expression preprocessing
│ ├── 05_reduce_pam50_to_corepam_FINAL.R CorePAM derivation (FREEZE)
│ ├── 06_*.R Z-score and scoring per cohort
│ ├── 07_*.R Survival analysis per cohort + figures
│ ├── 08_meta_survival.R Random-effects meta-analysis (K = 4 validation cohorts)
│ ├── 11_*.R Incremental value (delta-C-index) + DCA
│ ├── 13-16_*.R Quality control (correlations, PCA, schema, anti-hard-code)
│ ├── 17-18_*.R Manuscript rendering + submission bundle
│ ├── 19-23b_*.R pCR analysis block (download -> analysis -> meta -> figures)
│ ├── 30-36_*.R Major revision analyses (frozen z-score, Uno's C, calibration, ER subgroup, full baseline)
│ ├── 24_integrate_GSE1456_stockholm.R 4th validation cohort integration
│ └── 25-29_*.R Sensitivity analyses (CORE-A expanded, genefu, bootstrap, drop-gene)
│
├── results/
│ ├── corepam/ Model artefacts (weights, Pareto, training card)
│ ├── main/ Manuscript tables (Table 3, meta-analysis summary)
│ ├── supp/ Supplementary tables + QC reports
│ └── pcr/ pCR logistic results + meta-analysis
│
├── figures/
│ ├── main/en/{pdf,png}/ Main figures (vector + raster)
│ ├── supp/en/{pdf,png}/ Supplementary figures
│ └── pcr/en/{pdf,png}/ pCR figures
│
├── 01_docs/
│ ├── registry/ Frozen parameters, artefact inventory, audit reports
│ └── endpoint_mapping_templates/ Clinical endpoint mapping per cohort
│
├── manuscript/
│ └── CorePAM_manuscript.qmd Quarto source (all numbers read from CSVs)
│
├── submission/ Submitted manuscript (TeX, PDF, figures, supplementary files)
├── config/ pCR cohort manifest
├── CITATION.cff Citation metadata
├── INSTALL.R One-step package installer
├── LICENSE MIT License
└── README.md This file
Data lake (
01_Base_Pura_CorePAM/) is git-ignored. All raw and processed data are downloaded automatically byscripts/01_download_raw_data.R.
All analytical parameters were pre-specified and locked before any data analysis.
They are stored in 01_docs/registry/analysis_freeze.csv.
| Parameter | Value | Description |
|---|---|---|
delta_c |
0.010 | Non-inferiority margin for C-index |
alpha |
0.5 | Elastic-net mixing parameter |
k_folds |
10 | Stratified CV folds |
seed_folds |
42 | Fold assignment seed |
min_genes_fraction |
0.80 | Minimum gene coverage per cohort |
time_unit_divisor |
30.4375 | Days to months conversion |
| Component | Version |
|---|---|
| R | >= 4.4.0 (developed on 4.5.2) |
| Quarto | >= 1.4 (for manuscript rendering only) |
| Internet | Required for initial data download |
| Disk space | >= 15 GB |
| RAM | >= 16 GB |
source("INSTALL.R")Scripts are numbered and must be executed in order. Each script sources
00_setup.R automatically and checks whether its outputs already exist
(skip/force pattern — set FORCE_RERUN=TRUE to re-execute).
# Download raw data (requires internet, ~1-4 hours)
source("scripts/01_download_raw_data.R")
# Clinical harmonisation (4 cohorts)
source("scripts/02_harmonize_clinical_SCANB.R")
source("scripts/02_harmonize_clinical_TCGA_BRCA.R")
source("scripts/02_harmonize_clinical_METABRIC.R")
source("scripts/02_harmonize_clinical_GSE20685.R")
# Expression preprocessing (4 cohorts)
source("scripts/03_expression_preprocess_SCANB.R")
source("scripts/03_expression_preprocess_TCGA_BRCA.R")
source("scripts/03_expression_preprocess_METABRIC.R")
source("scripts/03_expression_preprocess_GSE20685.R")
# Gene audit + CorePAM derivation
source("scripts/04_gene_audit_freeze.R")
source("scripts/05_reduce_pam50_to_corepam_FINAL.R")
# Scoring (4 cohorts)
source("scripts/06_zscore_and_score_SCANB.R")
source("scripts/06_zscore_and_score_TCGA_BRCA.R")
source("scripts/06_zscore_and_score_METABRIC.R")
source("scripts/06_zscore_and_score_GSE20685.R")
# Survival analysis (preflight + 4 cohorts + meta)
source("scripts/07A_preflight_files_strict.R")
source("scripts/07_survival_analysis_SCANB.R")
source("scripts/07_survival_analysis_TCGA_BRCA.R")
source("scripts/07_survival_analysis_METABRIC.R")
source("scripts/07_survival_analysis_GSE20685.R")
source("scripts/08_meta_survival.R")
# Figures + tables
source("scripts/07x_extra_figures.R")
source("scripts/07y_pam50full_comparison.R")
source("scripts/07z_table_figures_survival.R")
# Incremental value + DCA
source("scripts/11_incremental_value_and_dca.R")
source("scripts/11b_dca_corepam.R")
# Quality control
source("scripts/13_qc_correlations_offdiag.R")
source("scripts/14_qc_metabric_pca_forensics.R")
source("scripts/15_qc_schema_range_checks.R")
source("scripts/16_qc_text_vs_results_assert.R")
# Manuscript rendering
source("scripts/17_render_manuscript_quarto.R")
# --- 4th validation cohort (GSE1456 / Stockholm) ---
source("scripts/24_integrate_GSE1456_stockholm.R")
# --- pCR block (secondary analysis) ---
source("scripts/19_download_pCR_raw_data.R")
source("scripts/20_prepare_pCR_GSE25066.R")
source("scripts/20_prepare_pCR_GSE20194.R")
source("scripts/20_prepare_pCR_GSE32646.R")
source("scripts/20_prepare_pCR_ISPY1.R")
source("scripts/20_prepare_pCR_ISPY2.R")
source("scripts/21_pCR_logistic_analysis.R")
source("scripts/21c_pcr_er_interaction.R")
source("scripts/21_ispy2_analysis.R")
source("scripts/22_meta_pCR.R")
source("scripts/22b_meta_pCR_with_ispy2.R")
source("scripts/23_pCR_figures.R")
source("scripts/23b_pCR_ispy2_figures.R")
# --- Sensitivity analyses ---
source("scripts/25_sensitivity_corea_expanded.R")
source("scripts/26_sensitivity_genefu_comparison.R")
source("scripts/27_bootstrap_gene_stability.R")
source("scripts/28_dropgene_sensitivity.R")
source("scripts/29_corea_sensitivity_forest.R")
# --- Major revision analyses ---
source("scripts/30_frozen_zscore_sensitivity.R")
source("scripts/31_uno_c_and_tdauc.R")
source("scripts/32_calibration_formal.R")
source("scripts/34_subgroup_er_stratified.R")
source("scripts/35_er_neg_bootstrap_stability.R")
source("scripts/36_full_clinical_baseline.R")source("scripts/00_validate_all.R")This checks syntax of all scripts and verifies that expected output files exist.
| Safeguard | Implementation |
|---|---|
| Frozen parameters | All choices locked in analysis_freeze.csv before analysis |
| SHA-256 audit trail | Every raw file and output artefact hashed in registry/study_registry.csv |
| No inter-cohort pooling | Expression matrices never combined; Z-scoring always intra-cohort |
| Strict I/O | options(warn = 2) — any warning is a fatal error |
| Skip/force pattern | Scripts check for existing outputs; FORCE_RERUN=TRUE to override |
| Anti-hard-code check | Script 16 verifies no manuscript number was typed manually |
| Leakage-proof | Training (SCAN-B) and validation cohorts are never mixed |
| No coefficient refitting | Validation cohorts use training weights only |
| Artefact | Path | Description |
|---|---|---|
CorePAM_weights.csv |
results/corepam/ |
24 genes with Cox elastic-net weights |
CorePAM_model.rds |
results/corepam/ |
Fitted glmnet model (git-ignored; regenerated by script 05) |
selected_CorePAM_summary.json |
results/corepam/ |
Derivation metadata (C-index, genes, lambda) |
pareto_df_cindex_oof.csv |
results/corepam/ |
Pareto frontier: gene count vs. OOF C-index |
Table3_*.csv |
results/main/ |
Survival performance by cohort (HR, C-index, events) |
meta_survival_summary.csv |
results/main/ |
Random-effects meta-analysis results |
pCR_results_by_cohort.csv |
results/pcr/ |
Logistic regression per pCR cohort |
meta_pCR_results.csv |
results/pcr/ |
pCR meta-analysis (OR, I2) |
For a given sample in cohort c:
G_present = CorePAM genes ∩ genes available in cohort c
z_i = intra-cohort Z-score for gene i
score = Σ(w_i · z_i) / Σ|w_i| (over G_present)
score_z = scale(score) (for HR per 1-SD)
Weights (w_i) are fixed from training. Score direction is established in SCAN-B
and never re-inverted per cohort.
All data are publicly available:
- SCAN-B: GSE96058
- TCGA-BRCA: GDC Data Portal
- METABRIC: cBioPortal
- GSE20685: GEO
- GSE1456: GEO
- GSE25066: GEO
- GSE20194: GEO
- GSE32646: GEO
- I-SPY 1: GSE22226
- I-SPY 2: GSE194040
| Problem | Solution |
|---|---|
| Package installation fails | Run source("INSTALL.R") in a clean R session |
| Download times out | Re-run 01_download_raw_data.R; the skip/force pattern resumes from where it stopped |
| "Output already exists" | Set FORCE_RERUN=TRUE environment variable to re-execute |
| Warning treated as error | This is by design (options(warn=2)). Investigate the warning — it may indicate a data issue |
| Memory error | Ensure >= 16 GB RAM. Close other applications before running preprocessing scripts |
If you use this work, please cite:
Botan RN, Sousa JB. CorePAM: a 24-gene PAM50-derived expression score with cross-platform external validation for breast cancer prognosis. Breast Cancer Res. 2026 (under review).
See also CITATION.cff for machine-readable citation metadata.
- Rafael de Negreiros Botan — oncologista@gmail.com
- Faculty of Medicine, University of Brasilia (UnB), Brazil
Analysis code: MIT License. Data are from public repositories (GEO, GDC, cBioPortal) under their respective terms of use.