5  Module 2: Classification Modeling — Quick Start

This chapter converts the cleaned Stat object into icare’s Train_Model object, selects informative features, performs a stratified development split, estimates preprocessing from training data, benchmarks common classifiers through the caret-based interface, and selects a final model using held-out AUC.

5.1 Important Notes

The Train_Model object is designed to preserve the modeling state needed for later prediction and deployment. Estimate imputation, scaling, feature selection, tuning, and decision thresholds from training data only, then apply the stored operations unchanged to the test set. Preserve this sequence when changing the data, class outcome, or resampling design.

5.2 Step-by-step Workflow

The workflow is divided into short stages. Each note explains the purpose, key checks, and interpretation limits. Generated plots and tables appear directly below their code chunk.

5.2.1 Environment and Global Setup

This section performs the core analysis for this stage. Review the output immediately below the code and confirm that data ranges, assumptions, and results match the study design.

Code
# =============================================================================
# icare Package Vignette — Module 2: Modeling Pipeline
# QUICK-START EDITION
# =============================================================================
# Audience : Users who just want a trained, evaluated model without exploring
#            every feature-selection method, ensemble strategy, or clinical
#            extra.
# Input    : "stat_obj.rds", produced by Module 1 (data cleaning).
#
# This script covers: convert to a modeling object -> select features ->
# split & scale -> train a few algorithms -> pick the best -> evaluate it.
#
# Looking for feature-selection comparisons, ensembles, tuning, SHAP
# explanations, or clinical thresholds/NRI? See
# "02_module2_modeling_ADVANCED.R" instead.
# =============================================================================
rm(list = ls())
library(icare)
library(caret)

set.seed(123)

# ---------------------------------------------------------------------------

5.2.2 1 Load the cleaned data and convert it into a modeling object

This section imports and organizes the data. Check dimensions and variable coding before continuing.

Code
# 1. Load the cleaned data and convert it into a modeling object
# ---------------------------------------------------------------------------
stat_obj  <- readRDS("stat_obj.rds")
model_obj <- ConvertObject(stat_obj, to = "Train_Model")
Data prepared for clean data from Stat object 
Model object created successfully.
Stat -> Train_Model conversion complete.
Code
model_obj@clean.df[[model_obj@group_col]] <- factor(model_obj@clean.df[[model_obj@group_col]])

# ---------------------------------------------------------------------------

5.2.3 2 Quick feature selection (built-in importance from a couple of models)

This section selects candidate features. Consider effect size, stability, and biological plausibility rather than a single significance measure.

Code
# 2. Quick feature selection (built-in importance from a couple of models)
# ---------------------------------------------------------------------------
builtin <- FeatureSelectBuiltin(
  object = model_obj,
  models = c("rf", "glm"),
  top_n  = 15,
  seed   = 123
)
Training rf for importance...
Training glm for importance...
Built-in (union): 20 features
Code
print(builtin$importance_table)
                                                                            Feature
Platelet_volume_distribution_width               Platelet_volume_distribution_width
Mean_corpuscular_hemoglobin_concentration Mean_corpuscular_hemoglobin_concentration
Mean_platelet_volume                                           Mean_platelet_volume
age                                                                             age
Lymphocytes_count                                                 Lymphocytes_count
Hematocrit                                                               Hematocrit
Platelet_count                                                       Platelet_count
Hemoglobin                                                               Hemoglobin
Eosinophil                                                               Eosinophil
Mean_red_blood_cell_volume                               Mean_red_blood_cell_volume
Lymphocytes                                                             Lymphocytes
Monocytes                                                                 Monocytes
Red_blood_cell_count                                           Red_blood_cell_count
Mean_cell_hemoglobin                                           Mean_cell_hemoglobin
PCT                                                                             PCT
Basophil_count                                                       Basophil_count
Monocytes_count                                                     Monocytes_count
sex2                                                                           sex2
Red_blood_cell_distribution_width_SD           Red_blood_cell_distribution_width_SD
Basophil                                                                   Basophil
                                           rf glm Selected
Platelet_volume_distribution_width        Yes Yes     [OK]
Mean_corpuscular_hemoglobin_concentration Yes Yes     [OK]
Mean_platelet_volume                      Yes Yes     [OK]
age                                       Yes Yes     [OK]
Lymphocytes_count                         Yes Yes     [OK]
Hematocrit                                Yes Yes     [OK]
Platelet_count                            Yes Yes     [OK]
Hemoglobin                                Yes Yes     [OK]
Eosinophil                                Yes Yes     [OK]
Mean_red_blood_cell_volume                Yes Yes     [OK]
Lymphocytes                               Yes   -     [OK]
Monocytes                                 Yes   -     [OK]
Red_blood_cell_count                      Yes   -     [OK]
Mean_cell_hemoglobin                      Yes   -     [OK]
PCT                                       Yes   -     [OK]
Basophil_count                              - Yes     [OK]
Monocytes_count                             - Yes     [OK]
sex2                                        - Yes     [OK]
Red_blood_cell_distribution_width_SD        - Yes     [OK]
Basophil                                    - Yes     [OK]
Code
# Keep only the top features identified above.
model_obj <- ApplyFeatureSelection(model_obj,builtin$importance_table$Feature[1:15])
Applied feature selection: 15 features kept.
Downstream slots (models, results) have been reset ; split.data/split.scale.data reset as well (no prior split found).
Code
colnames(model_obj@clean.df)
 [1] "Platelet_volume_distribution_width"       
 [2] "Mean_corpuscular_hemoglobin_concentration"
 [3] "Mean_platelet_volume"                     
 [4] "age"                                      
 [5] "Lymphocytes_count"                        
 [6] "Hematocrit"                               
 [7] "Platelet_count"                           
 [8] "Hemoglobin"                               
 [9] "Eosinophil"                               
[10] "Mean_red_blood_cell_volume"               
[11] "Lymphocytes"                              
[12] "Monocytes"                                
[13] "Red_blood_cell_count"                     
[14] "Mean_cell_hemoglobin"                     
[15] "PCT"                                      
[16] "group"                                    
Code
# ---------------------------------------------------------------------------

5.2.4 3 Split into training/testing sets and scale the predictors

This section creates training and validation sets. Restrict feature selection and parameter estimation to the training set to prevent leakage.

Code
# 3. Split into training/testing sets and scale the predictors
# ---------------------------------------------------------------------------
# Stratified split: create 70% training indices while preserving
#    the distribution of the grouping variable (e.g., response class)
#    using caret's createDataPartition.
#    - model_obj@clean.df: the cleaned data frame
#    - model_obj@group_col: name of the column used for stratification
#    - p = 0.7: proportion for training
#    - list = FALSE: returns a vector of indices instead of a list
idx <- createDataPartition(model_obj@clean.df[[model_obj@group_col]], p = 0.7, list = FALSE)
# Split the original clean data into training and testing sets
#    based on the indices, and store them in the 'split.data' slot
#    of the S4 object.
model_obj@split.data <- list(
  training = model_obj@clean.df[idx, ],
  testing  = model_obj@clean.df[-idx, ]
)
# Compute pre-processing parameters (center and scale) from the
#    training set, but ONLY on feature columns (exclude the grouping
#    column to avoid leaking the target/label information).
#    - setdiff() removes the group column from the feature set.
#    - method = c("center", "scale") standardises each feature to
#      mean = 0 and sd = 1.
preProc <- preProcess(
  model_obj@split.data$training[, setdiff(colnames(model_obj@split.data$training), model_obj@group_col)],
  method = c("center", "scale"))
#  Apply the same pre-processing parameters (estimated from the
#    training data) to BOTH training and testing sets, ensuring
#    that the testing data is transformed using the training
#    statistics (no data leakage). The results are stored in the
#    'split.scale.data' slot as a list.
model_obj@split.scale.data <- list(
  training = predict(preProc, model_obj@split.data$training),
  testing  = predict(preProc, model_obj@split.data$testing)
)
# Assign the scaled datasets to the 'filtered.set' slot.
#    This step may be used for subsequent modelling or further
#    feature filtering (e.g., removing near-zero variance predictors).
model_obj@filtered.set <- model_obj@split.scale.data

# ---------------------------------------------------------------------------

5.2.5 4 Train a handful of common algorithms and compare them

This section trains and compares candidate models. Select models using discrimination, calibration, stability, and clinical utility together.

Code
# 4. Train a handful of common algorithms and compare them
# ---------------------------------------------------------------------------
model_obj <- ModelTrainAnalysis(
  object   = model_obj,
  methods  = c("glmnet", "rf", "gbm"),
  save_plots = TRUE,
  seed     = 123,save_dir = "."
)
Train samples: 422  | Test samples: 179 
>>> Task type: binary 
>>> Selection metric: Accuracy 
Learning preprocessing ( center, scale ) from training data ( 15 feature(s))...
Preprocessing applied to 15 feature column(s).
Training models using CV...
Using repeatedcv with 10 folds and 3 repeats (n = 422 ) | folds shared across all methods for fair comparison 
  Training glmnet ... Done.
  Training rf ... Done.
  Training gbm ... Done.

========== Cross-Validation Performance ==========
   Model   CV_Mean      CV_SD
2     rf 0.9249854 0.04065092
3    gbm 0.9241539 0.04043910
1 glmnet 0.8776949 0.05194069

========== Final Evaluation on Holdout Test Set ==========
Test ROC     : 0.9634 
Test Accuracy: 0.8939 
Train AUC    : 1 
Gap          : 0.0366 (Acceptable)

Saving diagnostic plots to: . 
Plot saved to: ./roc_curves.pdf 
Plot data saved to: ./roc_curve_data.csv 
      gbm        rf    glmnet 
0.9645279 0.9633542 0.9337507 
CV comparison plot + data saved to: . 

>>> Train_Model object updated (binary mode).
Code
# ---------------------------------------------------------------------------

5.2.6 5 Pick the best model (by AUC) and look at its performance

This section trains and compares candidate models. Select models using discrimination, calibration, stability, and clinical utility together.

Code
# 5. Pick the best model (by AUC) and look at its performance
# ---------------------------------------------------------------------------
model_obj <- SelectBestModel(model_obj, metric = "auc")
Best model ( auc ):  gbm 
Code
cat("Best model:", model_obj@best.model.result$model_type, "\n")
Best model: gbm 
Code
PlotMultiROC(model_obj, test_data = model_obj@split.scale.data$testing, save_plot = TRUE)
Truth factor levels: Healthy Control, Case
Using 'Case' as positive class

Processing model: glmnet
  [OK] AUC = 0.934
Processing model: rf
  [OK] AUC = 0.963
Processing model: gbm
  [OK] AUC = 0.965
Plot saved to: /home/runner/work/icare/icare/Figures/Model/multi_ROC.pdf 

[OK] Plot saved to: /home/runner/work/icare/icare/Figures/Model
Code
PlotConfusionMatrix(model_obj, test_data = model_obj@split.scale.data$testing,
                    model_name = model_obj@best.model.result$model_type,
                    threshold = 0.5, save_plot = TRUE)
True labels - Levels: [Healthy Control, Case]
Using probability predictions with threshold = 0.500
Positive class: Case | Negative class: Healthy Control
Predicted labels (thresholded) - Levels: [Healthy Control, Case]
Final predicted levels: [Healthy Control, Case]

----------------------------------------
Performance Metrics (test set)
----------------------------------------
TP = 100 | TN = 61 | FP = 10 | FN = 8
Total N = 179
Accuracy     = 0.899 (89.9%)
Sensitivity  = 0.926 (92.6%)   [Recall / TPR]
Specificity  = 0.859 (85.9%)   [TNR]
PPV (Precision) = 0.909 (90.9%)
NPV             = 0.884 (88.4%)
Prevalence (test) = 0.603 (60.3%)
[OK] Plot saved to: /home/runner/work/icare/icare/Figures/Model/confusion_gbm_thr050.pdf
Code
PlotFeatureImportance(model_obj, top_n = 15, save_plot = TRUE)
Plot saved to: /home/runner/work/icare/icare/Figures/Model/feature_importance.pdf 
Code
# ---------------------------------------------------------------------------

5.2.7 6 Save your trained model

This section trains and compares candidate models. Select models using discrimination, calibration, stability, and clinical utility together.

Code
# 6. Save your trained model
# ---------------------------------------------------------------------------
saveRDS(model_obj, file = "model_obj.rds")

cat("\nDone! model_obj.rds saved.\n",
    "Want ensembles, tuning, SHAP explanations, or clinical thresholds?",
    "See the ADVANCED version of this script.\n")

Done! model_obj.rds saved.
 Want ensembles, tuning, SHAP explanations, or clinical thresholds? See the ADVANCED version of this script.