4  Module 1: Data Cleaning and Exploration — Advanced

The advanced Stat workflow covers raw-data diagnostics, automatic and manual type handling, missing-value imputation, outlier treatment, one-hot encoding, normalization, grouped plots, correlation heatmaps, PCA, differential features, ROC analysis, radar plots, and Table 1. Together, these steps implement the package’s clinical-statistics and exploratory-analysis layer before downstream prediction or subtyping.

4.1 Key Configuration

Set GROUP_COL, OUTPUT_DIR, and N_TOP_FEATURES in the CONFIG section. Review every inferred data type and retain a record of imputation and scaling decisions. Outputs are organized into subdirectories so that quality-control results, statistical summaries, and publication figures can be audited separately.

4.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.

4.2.1 About this chapter

Audience. Advanced users preparing figures and tables for a manuscript, or anyone who wants to see every configurable option in the Module 1 pipeline.

Input. Reads data/PMID37633276_DIA_plasmaproomic.xlsx (sheet 3) directly.

The chapter runs end to end as one session:

  1. CONFIG — the only section you should need to edit for a new dataset
  2. Load Data
  3. Create the Stat Object
  4. Pre-cleaning Exploratory Analysis
  5. Data Cleaning Pipeline
  6. Publication-Quality Visualizations
  7. Additional Descriptive Visualizations
  8. Differential Feature Analysis (Wilcoxon Test)
  9. Differential-Feature Visualizations
  10. Reporting & Result Export

4.2.2 0 CONFIG — the only section you should need to edit for a new dataset

This section defines seeds, output paths, and dependencies. Update these paths and key parameters first when using new data.

Code
# 0. CONFIG — the only section you should need to edit for a new dataset
# ---------------------------------------------------------------------------
options(warn = -1)
rm(list = ls())
library(icare)

GROUP_COL  <- "group"                              # outcome / group column
OUTPUT_DIR <- "./Module1_Advanced_Output"                  # all figures/tables go here
N_TOP_FEATURES <- 10                                       # how many DEGs to carry into plots

dir.create(OUTPUT_DIR, showWarnings = FALSE, recursive = TRUE)
# Register OUTPUT_DIR with the package rather than defining a local sub_dir().
# icare already ships sub_dir(); a local definition shadows it only inside this
# script, so functions INSIDE the package that call sub_dir() themselves would
# still resolve it in the icare namespace and write somewhere else. Calling
# set_output_root() points both this script and the package at the same tree.
set_output_root(OUTPUT_DIR)

# =============================================================================

4.2.3 1 Load Data

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

Code
# 1. Load Data
# =============================================================================
# stringsAsFactors = FALSE keeps everything as character/numeric for now;
library(readxl)
raw_dis=as.data.frame(read_excel("data/PMID37633276_DIA_plasmaproomic.xlsx",sheet = 3))
raw_dis=raw_dis[!grepl(x = raw_dis$`Case ID`,pattern = "^KC"),]
dput(colnames(raw_dis))
c("Case ID", "Sample type", "Plasma proteome ID", "Tissue proteome ID", 
"Gender", "Age at diagnosis", "Tobacco smoking history", "Drinking history", 
"Vital status", "Days to last followup", "Days until death", 
"Combined days to last followup or death", "Progressive-free survival_Status", 
"Progressive-free survival_Months", "Tumor location", "Tumor size (cm)", 
"T-category", "Lymph node involvement", "Distant metastasis", 
"TNM Stage", "Grade", "Morphology", "White blood cell count", 
"Lymphocytes%", "Monocytes%", "Neutrophilic granulocyte%", "Eosinophil%", 
"Basophil%", "Lymphocytes count", "Monocytes count", "Neutrophilic granulocyte count", 
"Eosinophil count", "Basophil count", "Red blood cell count", 
"Hemoglobin", "Hematocrit", "Mean red blood cell volume", "Mean cell hemoglobin", 
"Mean corpuscular hemoglobin concentration", "Red blood cell distribution width CV", 
"Red blood cell distribution width SD", "Platelet count", "Mean platelet volume", 
"PCT", "Platelet volume distribution width", "Alkaline phosphatase", 
"Alanine aminotransferase", "Aspartate aminotransferase", "Isoenzyme of millet grass", 
"Lactate dehydrogenase", "Glutamyl transpeptidase", "Total serum protein", 
"Albumin", "Globulin", "Albumin/Globulin", "Prealbumin", "Urea", 
"Crea", "Uricemia", "Ca", "P", "Mg", "K", "Na", "CL", "C-Reactive protein", 
"Total CO2", "Total bilirubin", "Direct bilirubin", "Prothrombin time", 
"Fibrinogen", "Activated partial thromboplatin time", "Thrombin time", 
"Prothrombin Time Intertional Normalized Ratio")
Code
mat=raw_dis[,c( "White blood cell count", 
                "Lymphocytes%", "Monocytes%", "Neutrophilic granulocyte%", "Eosinophil%", 
                "Basophil%", "Lymphocytes count", "Monocytes count", "Neutrophilic granulocyte count", 
                "Eosinophil count", "Basophil count", "Red blood cell count", 
                "Hemoglobin", "Hematocrit", "Mean red blood cell volume", "Mean cell hemoglobin", 
                "Mean corpuscular hemoglobin concentration", "Red blood cell distribution width CV", 
                "Red blood cell distribution width SD", "Platelet count", "Mean platelet volume", 
                "PCT", "Platelet volume distribution width")]
index=grepl(x = raw_dis$`Case ID`,pattern = "^HC")
inf=raw_dis[,c("Case ID", "Sample type", "Plasma proteome ID", "Tissue proteome ID", 
               "Gender", "Age at diagnosis", "Tobacco smoking history", "Drinking history", 
               "Vital status", "Days to last followup", "Days until death", 
               "Combined days to last followup or death", "Progressive-free survival_Status", 
               "Progressive-free survival_Months", "Tumor location", "Tumor size (cm)", 
               "T-category", "Lymph node involvement", "Distant metastasis", 
               "TNM Stage", "Grade", "Morphology")]
# The group label goes in `inf` ONLY. icare stores the label in info.data and
# keeps it out of the feature matrix entirely: a label sitting among the
# features is indistinguishable from a feature to any code that selects columns
# by type, and a numeric 0/1 label would silently be fed into PCA and the
# correlation heatmap. Because the label never enters `mat`, its R type is free
# -- labels can be readable strings, which is what you want in the Table 1
# column headers.
inf$group = factor(ifelse(index, "Healthy Control", "Case"),
                   levels = c("Healthy Control", "Case"))

# `mat` stays purely numeric, so as.matrix()/storage.mode() is a safe no-op and
# any non-numeric column here would be a genuine mistake rather than a silent NA.
mat=as.matrix(mat)
storage.mode(mat) <- "numeric"

rownames(mat)=inf$`Case ID`
rownames(inf)=inf$`Case ID`
mat=as.data.frame(mat)
mat$age=inf$`Age at diagnosis`
mat$sex=inf$Gender
# =============================================================================

4.2.4 2 Create the Stat Object

This section creates the icare analysis object that stores data, metadata, and workflow state for all later steps.

Code
# 2. Create the Stat Object
# =============================================================================
# CreateStatObject() is the entry point of icare: it stores the raw data,
# remembers which column is the grouping/outcome variable, and will
# accumulate every cleaning step you apply to it (accessible via
# stat_obj@clean.data once cleaning is done).
# na.action = "allow" means missing values are kept for now; we handle them
# explicitly in the cleaning section (step 4.2) rather than dropping rows here.
stat_obj <- CreateStatObject(
  raw.data  = mat,info.data = inf,
  group_col = "group",
  na.action = "allow"
)
Data prepared for raw.data 
Data prepared for info.data 
Final info.data prepared.
Stat object created.
Code
cat("Stat object created.\n")
Stat object created.
Code
# =============================================================================

4.2.5 3 Pre-cleaning Exploratory Analysis

This section preprocesses the data. Review missingness, outlier handling, and normalization before accepting the defaults.

Code
# 3. Pre-cleaning Exploratory Analysis
# =============================================================================
## 3.1 Missing-data visualization — shows which variables/rows are most
##     affected, which should guide your imputation strategy in step 4.2
stat_obj <- state_plot_missing_data(
  stat_obj,
  save_plots = TRUE,
  save_data  = TRUE,
  save_dir   = sub_dir("01_exploration", "missing_data")
)
No missing values in the data.$var_plot_obj
NULL

$sample_plot_obj
NULL

$combined_plot
NULL

Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'process.info' slot updated.
Code
## 3.2 Variable type diagnosis — auto-detects numeric vs. categorical columns.
## max_unique_values = 5 means any column with <= 5 unique values is treated
## as categorical even if it is stored as numbers (e.g. a 0/1/2 coded score).
stat_obj <- stat_diagnose_variable_type(stat_obj, max_unique_values = 5)
Diagnosed variable types:
Numeric variables: 24 
Categorical variables: 1 
Variables flagged for encoding: 0 
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'variable.types' slot updated.
Code
cat("Detected variable types:\n"); print(stat_obj@variable.types)
Detected variable types:
$numeric_vars
 [1] "White_blood_cell_count"                   
 [2] "Lymphocytes"                              
 [3] "Monocytes"                                
 [4] "Neutrophilic_granulocyte"                 
 [5] "Eosinophil"                               
 [6] "Basophil"                                 
 [7] "Lymphocytes_count"                        
 [8] "Monocytes_count"                          
 [9] "Neutrophilic_granulocyte_count"           
[10] "Eosinophil_count"                         
[11] "Basophil_count"                           
[12] "Red_blood_cell_count"                     
[13] "Hemoglobin"                               
[14] "Hematocrit"                               
[15] "Mean_red_blood_cell_volume"               
[16] "Mean_cell_hemoglobin"                     
[17] "Mean_corpuscular_hemoglobin_concentration"
[18] "Red_blood_cell_distribution_width_CV"     
[19] "Red_blood_cell_distribution_width_SD"     
[20] "Platelet_count"                           
[21] "Mean_platelet_volume"                     
[22] "PCT"                                      
[23] "Platelet_volume_distribution_width"       
[24] "age"                                      

$categorical_vars
[1] "sex"

$vars_to_encode
character(0)
Code
## 3.3 Descriptive statistics on the RAW data, before any cleaning.
## Keeping a "before" summary alongside the "after" one (produced later) is
## good practice for a methods section: it lets you report what changed.
stat_obj <- stat_compute_descriptive(stat_obj, count_feature = TRUE)
Categorical variables identified: sex 
Group counts computed for column: group 
Counting categorical values...
Checking normality for numeric column: White_blood_cell_count 
Checking normality for numeric column: Lymphocytes 
Checking normality for numeric column: Monocytes 
Checking normality for numeric column: Neutrophilic_granulocyte 
Checking normality for numeric column: Eosinophil 
Checking normality for numeric column: Basophil 
Checking normality for numeric column: Lymphocytes_count 
Checking normality for numeric column: Monocytes_count 
Checking normality for numeric column: Neutrophilic_granulocyte_count 
Checking normality for numeric column: Eosinophil_count 
Checking normality for numeric column: Basophil_count 
Checking normality for numeric column: Red_blood_cell_count 
Checking normality for numeric column: Hemoglobin 
Checking normality for numeric column: Hematocrit 
Checking normality for numeric column: Mean_red_blood_cell_volume 
Checking normality for numeric column: Mean_cell_hemoglobin 
Checking normality for numeric column: Mean_corpuscular_hemoglobin_concentration 
Checking normality for numeric column: Red_blood_cell_distribution_width_CV 
Checking normality for numeric column: Red_blood_cell_distribution_width_SD 
Checking normality for numeric column: Platelet_count 
Checking normality for numeric column: Mean_platelet_volume 
Checking normality for numeric column: PCT 
Checking normality for numeric column: Platelet_volume_distribution_width 
Checking normality for numeric column: age 
Computing descriptive statistics for numeric columns...
Descriptive statistics computation completed.
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'compute.descriptive' slot updated.
Code
# =============================================================================

4.2.6 4 Data Cleaning Pipeline

This section preprocesses the data. Review missingness, outlier handling, and normalization before accepting the defaults.

Code
# 4. Data Cleaning Pipeline
# =============================================================================
## 4.1 Convert columns to their diagnosed types (numeric / factor)
stat_obj <- stat_convert_variables(stat_obj, save_data = FALSE)
Converted  White_blood_cell_count  to numeric.
Converted  Lymphocytes  to numeric.
Converted  Monocytes  to numeric.
Converted  Neutrophilic_granulocyte  to numeric.
Converted  Eosinophil  to numeric.
Converted  Basophil  to numeric.
Converted  Lymphocytes_count  to numeric.
Converted  Monocytes_count  to numeric.
Converted  Neutrophilic_granulocyte_count  to numeric.
Converted  Eosinophil_count  to numeric.
Converted  Basophil_count  to numeric.
Converted  Red_blood_cell_count  to numeric.
Converted  Hemoglobin  to numeric.
Converted  Hematocrit  to numeric.
Converted  Mean_red_blood_cell_volume  to numeric.
Converted  Mean_cell_hemoglobin  to numeric.
Converted  Mean_corpuscular_hemoglobin_concentration  to numeric.
Converted  Red_blood_cell_distribution_width_CV  to numeric.
Converted  Red_blood_cell_distribution_width_SD  to numeric.
Converted  Platelet_count  to numeric.
Converted  Mean_platelet_volume  to numeric.
Converted  PCT  to numeric.
Converted  Platelet_volume_distribution_width  to numeric.
Converted  age  to numeric.
Converted sex to factor.
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'clean.data' slot updated.
Code
## 4.2 Impute missing values.
## "median_mode" = median for numeric columns, mode for categorical ones —
## a simple, fast, and reasonably robust default. For datasets with more
## complex/structured missingness, consider impute_method = "mice" instead
## (see the alternative Module 1 script for a worked example).
stat_obj <- stat_miss_processed(
  stat_obj,
  impute_method = "mice",
  return_imputation_info = TRUE,
  save_data = FALSE
)
Performing multiple imputation using MICE...

 iter imp variable
  1   1
  1   2
  1   3
  1   4
  1   5
  2   1
  2   2
  2   3
  2   4
  2   5
  3   1
  3   2
  3   3
  3   4
  3   5
  4   1
  4   2
  4   3
  4   4
  4   5
  5   1
  5   2
  5   3
  5   4
  5   5
Imputed data (MICE) generated. Now performing median/mode imputation for remaining NAs...
Imputation completed successfully.
Updating 'Stat' object...
- 'clean.data' slot updated.
- 'process.info' slot updated.
Code
## 4.3 Detect outliers using the IQR rule (values beyond 1.5x the
## interquartile range from Q1/Q3 are flagged, the usual Tukey convention).
stat_obj <- stat_detect_and_mark_outliers(
  stat_obj,
  method    = "iqr",
  threshold = 1.5,
  save_data = FALSE
)
Updating 'Stat' object...
- 'process.info' slot updated.
Code
## 4.4 Handle the flagged outliers by replacing them with the column median
## rather than deleting rows, so no samples are lost.
stat_obj <- stat_handle_outliers(
  stat_obj,
  method       = "impute",
  impute_value = "median",
  save_data    = FALSE
)
Imputed outliers in column: White_blood_cell_count with median 
Imputed outliers in column: Lymphocytes with median 
Imputed outliers in column: Monocytes with median 
Imputed outliers in column: Neutrophilic_granulocyte with median 
Imputed outliers in column: Eosinophil with median 
Imputed outliers in column: Basophil with median 
Imputed outliers in column: Lymphocytes_count with median 
Imputed outliers in column: Monocytes_count with median 
Imputed outliers in column: Neutrophilic_granulocyte_count with median 
Imputed outliers in column: Eosinophil_count with median 
Imputed outliers in column: Basophil_count with median 
Imputed outliers in column: Red_blood_cell_count with median 
Imputed outliers in column: Hemoglobin with median 
Imputed outliers in column: Hematocrit with median 
Imputed outliers in column: Mean_red_blood_cell_volume with median 
Imputed outliers in column: Mean_cell_hemoglobin with median 
Imputed outliers in column: Mean_corpuscular_hemoglobin_concentration with median 
Imputed outliers in column: Red_blood_cell_distribution_width_CV with median 
Imputed outliers in column: Red_blood_cell_distribution_width_SD with median 
Imputed outliers in column: Platelet_count with median 
Imputed outliers in column: Mean_platelet_volume with median 
Imputed outliers in column: PCT with median 
Imputed outliers in column: Platelet_volume_distribution_width with median 
Updating 'Stat' object...
- 'clean.data' slot updated.
- 'process.info' slot updated.
Code
## 4.5 One-hot encode categorical variables that have more than 2 levels
## (binary variables are left as single 0/1 columns to avoid redundancy).
stat_obj <- stat_onehot_encode(stat_obj, save_data = FALSE)
Input object class: Stat 
Starting one-hot encoding on data...
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'clean.data' slot updated.
Code
## 4.6 Normalize numeric features. method = "auto" lets icare pick an
## appropriate transform per-column (e.g. z-score vs. a skew-correcting
## transform) based on each variable's distribution.
stat_obj <- stat_normalize_process(stat_obj, method = "auto", save_data = FALSE)
Updating 'Stat' object...
- 'scale.data' slot updated.
- 'process.info' slot updated.
Code
cat("\nCleaning completed. Clean data dimensions:",
    nrow(stat_obj@clean.data), "x", ncol(stat_obj@clean.data), "\n")

Cleaning completed. Clean data dimensions: 601 x 25 
Code
# =============================================================================

4.2.7 5 Publication-Quality Visualizations

This section creates exploratory and reporting figures. Plots appear below the code and are also saved as vector PDFs for publication.

Code
# 5. Publication-Quality Visualizations
# =============================================================================
viz_dir <- sub_dir("02_visualizations")

## 5.1 Grouped box + violin plots for the first 6 numeric variables, each
## annotated with a Wilcoxon test p-value between groups. Swap `features`
## for any variable list you want to feature in a figure.
numeric_vars <- names(stat_obj@clean.data)[sapply(stat_obj@clean.data, is.numeric)]
# Key these by the label text, not by a code: the group column holds
# "Healthy Control"/"Case", and unmatched keys would silently fall back.
my_colors <- c("Healthy Control" = "#404040", "Case" = "#ca0020")
PlotGroupedDistribution(
  object       = stat_obj,
  features     = head(numeric_vars, 6),
  group_col    = "group",    
  group_colors = my_colors,
  test         = "wilcox.test",
  palette_name = "Set1", height = 8,width = 10,     
  ncol         = 3,
  save_dir     = viz_dir,
  save_plot    = TRUE
)
Generating grouped distribution plot...
Plot saved to: ./Module1_Advanced_Output/02_visualizations/grouped_distribution.pdf 
Code
unique(stat_obj@clean.data$group)
NULL
Code
## 5.2 Correlation heatmap for a clinically meaningful feature panel
## (here: red-cell indices). Replace `features` with your variables of interest.
PlotCorrelationHeatmap(
  object       = stat_obj,insig = "blank",pch = 3,
  features     = numeric_vars[-24],
  color_scheme = "gradient",   # alternative: "brew"
  save_dir     = viz_dir,
  save_plot    = TRUE
)
Generating correlation heatmap (corrplot backend)...
Original data dimensions: 601 x 25 
Selected features: 23 
Data dimensions after NA handling: 601 x 23 
Correlation matrix computed successfully ( 23 x 23 )
P-value matrix computed successfully.
Note: Using order='hclust' with p-value masking.  P-values are aligned based on clustering order.
Drawing correlation heatmap...
[OK] Heatmap saved to: ./Module1_Advanced_Output/02_visualizations/correlation_heatmap_20261016_060537.pdf 
[OK] Correlation heatmap completed successfully.
Code
## 5.3 PCA scatter plot coloured by the outcome group, with 95% confidence
## ellipses — a standard "does the outcome separate along the main axes of
## variation" sanity check before modeling.
# Key these by the label text, not by a code: the group column holds
# "Healthy Control"/"Case", and unmatched keys would silently fall back.
my_colors <- c("Healthy Control" = "#404040", "Case" = "#ca0020")
PlotPCA(
  object       = stat_obj,
  color_by     = "group",          
  group_colors = my_colors,
  pcs          = c(1, 2),
  ellipse      = TRUE,
  palette_name = "Royal1",
  save_dir     = viz_dir,
  save_plot    = TRUE
)
Generating PCA plot...
Plot saved to: ./Module1_Advanced_Output/02_visualizations/PCA.pdf 
Code
# =============================================================================

4.2.8 6 Additional Descriptive Visualizations

This section creates exploratory and reporting figures. Plots appear below the code and are also saved as vector PDFs for publication.

Code
# 6. Additional Descriptive Visualizations
# =============================================================================
## 6.1 Bar plots for every categorical variable
stat_obj <- plot_categorical_descriptive(stat_obj, save_plots = TRUE, save_dir = viz_dir)
Processing column: sex 
Saved plot for column: sex 
A total of 1 plots were generated.
Saved plots to: ./Module1_Advanced_Output/02_visualizations 
Updating 'Stat' object...
- 'compute.descriptive' slot updated.
Code
## 6.2 Violin plots for every numeric variable (one plot per variable)
# Key these by the label text, not by a code: the group column holds
# "Healthy Control"/"Case", and unmatched keys would silently fall back.
my_colors <- c("Healthy Control" = "#404040", "Case" = "#ca0020")
stat_obj <- plot_numeric_descriptive(
  stat_obj, plot_type = "violin", vars_per_plot = 1,group_col = 'group',group_colors = my_colors,
  save_plots = TRUE, save_dir = viz_dir
)
Input is an object of class 'Stat'. Extracting data...
Generating violin plots...
Saved violin plot part 1 
Saved violin plot part 2 
Saved violin plot part 3 
Saved violin plot part 4 
Saved violin plot part 5 
Saved violin plot part 6 
Saved violin plot part 7 
Saved violin plot part 8 
Saved violin plot part 9 
Saved violin plot part 10 
Saved violin plot part 11 
Saved violin plot part 12 
Saved violin plot part 13 
Saved violin plot part 14 
Saved violin plot part 15 
Saved violin plot part 16 
Saved violin plot part 17 
Saved violin plot part 18 
Saved violin plot part 19 
Saved violin plot part 20 
Saved violin plot part 21 
Saved violin plot part 22 
Saved violin plot part 23 
Saved violin plot part 24 
A total of 24 plots were generated.
Updating 'Stat' object...
- 'compute.descriptive' slot updated.
Code
## 6.3 Ridge (density) plots — an alternative view of the same numeric
## variables, useful when distributions are multi-modal.
library(ggridges)
stat_obj <- plot_numeric_descriptive(
  stat_obj, plot_type = "ridge", vars_per_plot = 1,
  save_plots = TRUE, save_dir = viz_dir
)
Input is an object of class 'Stat'. Extracting data...
Generating ridge density plots...
Saved ridge plot part 1 
Saved ridge plot part 2 
Saved ridge plot part 3 
Saved ridge plot part 4 
Saved ridge plot part 5 
Saved ridge plot part 6 
Saved ridge plot part 7 
Saved ridge plot part 8 
Saved ridge plot part 9 
Saved ridge plot part 10 
Saved ridge plot part 11 
Saved ridge plot part 12 
Saved ridge plot part 13 
Saved ridge plot part 14 
Saved ridge plot part 15 
Saved ridge plot part 16 
Saved ridge plot part 17 
Saved ridge plot part 18 
Saved ridge plot part 19 
Saved ridge plot part 20 
Saved ridge plot part 21 
Saved ridge plot part 22 
Saved ridge plot part 23 
Saved ridge plot part 24 
A total of 24 plots were generated.
Updating 'Stat' object...
- 'compute.descriptive' slot updated.
Code
# =============================================================================

4.2.9 7 Differential Feature Analysis (Wilcoxon Test)

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

Code
# 7. Differential Feature Analysis (Wilcoxon Test)
# =============================================================================
deg_dir <- sub_dir("03_differential_analysis")
stat_obj <- stat_var_feature(stat_obj, p_threshold = 0.05, logfc_threshold = 0.1,save_data = TRUE)
DEG results will be saved to: ./Module1_Advanced_Output/m1/deg_results 
Starting batch Wilcoxon test...
  Test data  :  601 samples x 25 features
  logFC data :  same as test data 
  logFC type : log2ratio 
DEG results saved to: ./Module1_Advanced_Output/m1/deg_results/last_test_sig.csv 
Feature selection completed. Significant features: 19 
'var.result' slot updated.
Code
deg_result <- ExtractLastTestSig(stat_obj)
# Standardize the "feature name" column (some downstream plotting functions
# expect a column literally called `feature`).
deg_result$feature <- deg_result$id

# Rank features by adjusted p-value and keep the top N for downstream plots —
# this replaces any ad hoc/undefined feature list with an explicit, reproducible one.
top_ids <- head(deg_result$id[order(deg_result$p.adjust)], N_TOP_FEATURES)

# =============================================================================

4.2.10 8 Differential-Feature Visualizations

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

Code
# 8. Differential-Feature Visualizations
# =============================================================================
## 8.1 AUC vs. p-value scatter — flags features that are both statistically
## significant AND discriminative (AUC far from 0.5).
PlotAUCPval(
  deg_df     = deg_result,
  # PlotAUCPval() takes a plain data frame and expects the label alongside the
  # features. Since Stat@clean.data is label-free by contract, join it back from
  # info.data for this call (the public helper .stat_with_group does exactly this).
  mat_test   = icare:::.stat_with_group(stat_obj),
  group_col  = GROUP_COL,base_size = 8,text_repel_size = 2,
  auc_thresh = 0.55,
  p_thresh   = 0.05,
  save_dir   = deg_dir,
  save_plot  = TRUE
)
Generating AUC-P value selection plot...
Selected features: 19 
Plot saved to: ./Module1_Advanced_Output/03_differential_analysis/PlotAUCPval.pdf 
Code
## 8.2 Heatmap of the top differential features across samples
# Build the top annotation (assuming rownames(mat) contains sample IDs)
top_anno <- stat_obj@info.data
# info.data column names are normalised by CreateStatObject() (spaces become
# underscores), so reference the normalised names, not the original Excel header.
top_anno <-top_anno [,c('Gender','Age_at_diagnosis')]
# Custom color palette
custom_cols <- list(
  group= c("Case" = "#404040", "Healthy Control" = "#ca0020"),
  Gender= c( Female= "#018571", Male = "#a6611a"),
  `Age_at_diagnosis`= circlize::colorRamp2(c(40, 55, 70), c("#edf8b1", "#7fcdbb", "#2c7fb8"))
)
PlotFeatureHeatmap(
  object = stat_obj,
  features = top_ids,   
  top_annotation = top_anno,
  ann_colors = custom_cols,
  cluster_rows   = TRUE,
  show_rownames  = TRUE,
  save_dir       = deg_dir,
  color_palette = c("lightgray", "#7B1FA2", "black", "gold") ,
  save_plot      = TRUE
)
Generating feature heatmap...
Heatmap saved to: ./Module1_Advanced_Output/03_differential_analysis/feature_heatmap.pdf 
Code
## 8.3 ROC curves for the top differential features (per-feature discrimination)
library(pROC)
stat_obj <- VarFeature_ROC(stat_obj, data_type = "clean", save_dir = deg_dir, save_plots = TRUE,
                           palette_name = 'Paired',base_size = 8)
Filtered features (for_label):
# A tibble: 10 × 12
# Groups:   change [2]
   id              W        p  mean_x  mean_y median_x median_y p.adjust sd_Case
   <chr>       <dbl>    <dbl>   <dbl>   <dbl>    <dbl>    <dbl>    <dbl> <chr>  
 1 Eosinophil 52567  7.83e- 6 2.25e+0 1.75e+0     1.9      1.5  1.71e- 5 1.3949…
 2 Eosinophi… 50977  2.08e- 4 1.37e-1 1.14e-1     0.11     0.09 3.84e- 4 0.0838…
 3 Basophil   48524. 1.07e- 2 4.35e-1 3.80e-1     0.4      0.4  1.51e- 2 0.2325…
 4 Monocytes  57245  1.88e-11 6.50e+0 5.69e+0     6.3      5.6  6.45e-11 1.4439…
 5 Basophil_… 47894. 2.25e- 2 2.82e-2 2.47e-2     0.02     0.02 2.84e- 2 0.0159…
 6 Platelet_… 12688. 4.92e-51 1.30e+1 1.57e+1    13       16    1.18e-49 2.2543…
 7 Lymphocyt… 27085  7.57e-15 1.69e+0 2.03e+0     1.6      2.1  6.05e-14 0.5596…
 8 Lymphocyt… 29504. 4.04e-11 2.77e+1 3.17e+1    27.9     31.7  1.21e-10 7.8078…
 9 Platelet_… 29170. 1.36e-11 2.21e+2 2.53e+2   213      251    5.43e-11 61.809…
10 PCT        32644  3.33e- 7 2.32e-1 2.54e-1     0.23     0.25 8.87e- 7 0.0579…
# ℹ 3 more variables: `sd_Healthy Control` <chr>, logFC <dbl>, change <fct>
Processing feature: Eosinophil 
Processing feature: Eosinophil_count 
Processing feature: Basophil 
Processing feature: Monocytes 
Processing feature: Basophil_count 
Processing feature: Platelet_volume_distribution_width 
Processing feature: Lymphocytes_count 
Processing feature: Lymphocytes 
Processing feature: Platelet_count 
Processing feature: PCT 
Saved plots to:  ./Module1_Advanced_Output/03_differential_analysis/roc_plot.pdf 
Code
## 8.4 Radar chart summarizing mean expression shifts between groups
stat_obj <- VarFeature_radarchart(stat_obj, save_dir = deg_dir, save_plots = TRUE,drop_stable = F,base_size = 6,
                                  sort_by = 'pvalue',plot_width = 8,plot_height = 8,title = '')
Generating variable feature radar chart...
Radar chart saved to: ./Module1_Advanced_Output/03_differential_analysis/radar_chart.pdf 
Code
## 8.5 Boxplots for the top 3 differential features (raw/clean scale)
p_box <- PlotDegBoxplot(
  deg_results = deg_result,
  # PlotDegBoxplot() takes a plain data frame and needs the label beside the
  # features; Stat@clean.data is label-free by contract, so join it back.
  expr_data   = icare:::.stat_with_group(stat_obj),
  group_col   = GROUP_COL,
  top_n       = 5,
  save_plot   = TRUE,
  save_dir    = deg_dir
)
Generating DEG boxplot...
Plot saved to: ./Module1_Advanced_Output/03_differential_analysis/deg_boxplot.pdf 
Code
# =============================================================================

4.2.11 9 Reporting & Result Export

This section saves model objects, tables, and figures for reproducibility, review, and reuse.

Code
# 9. Reporting & Result Export
# =============================================================================
## 9.1 Baseline characteristics table ("Table 1" for a manuscript), written
## to a Word document for easy copy-paste into a submission.
stat_obj <- stat_gaze_analysis(stat_obj, save_word = TRUE, save_dir = OUTPUT_DIR)
Starting stat_gaze_analysis (safe version)...
Data prepared for gaze analysis. Number of rows: 601 Columns: 26 
Using formula with one group: group ~ . 
Running gaze analysis with method: 3 
Result type: gaze data.frame tibble 
Gaze analysis completed successfully.
Word file saved to: ./Module1_Advanced_Output/gaze_analysis.docx 
a flextable object.
col_keys: `name`, `levels`, `Healthy Control (N=239)`, `Case (N=362)`, `p` 
header has 1 row(s) 
body has 26 row(s) 
original dataset sample: 
Classes 'gaze', 'tibble' and 'data.frame':  26 obs. of  5 variables:
 $ name                   : chr  "White_blood_cell_count" "Lymphocytes" "Monocytes" "Neutrophilic_granulocyte" ...
 $ levels                 : chr  "Median (IQR)" "Mean ± SD" "Median (IQR)" "Mean ± SD" ...
 $ Healthy Control (N=239): chr  "6.5 (5.7 to 7.5)" "31.7 ± 7.0" "5.6 (4.8 to 6.5)" "60.3 ± 7.6" ...
 $ Case (N=362)           : chr  "6.3 (5.1 to 7.3)" "27.7 ± 7.8" "6.3 (5.5 to 7.5)" "62.3 ± 8.3" ...
 $ p                      : chr  ".022" "<.001" "<.001" ".004" ...
 - attr(*, "groups")= tibble [2 × 2] (S3: tbl_df/tbl/data.frame)
  ..$ group: Factor w/ 2 levels "Healthy Control",..: 1 2
  ..$ n    : chr [1:2] "(N=239)" "(N=362)"
 - attr(*, "yvars")= chr "group"
'baseline.table' slot updated.
Code
cat("\nBaseline table generated.\n")

Baseline table generated.
Code
if (!is.null(stat_obj@baseline.table)) print(stat_obj@baseline.table)
a flextable object.
col_keys: `name`, `levels`, `Healthy Control (N=239)`, `Case (N=362)`, `p` 
header has 1 row(s) 
body has 26 row(s) 
original dataset sample: 
Classes 'gaze', 'tibble' and 'data.frame':  26 obs. of  5 variables:
 $ name                   : chr  "White_blood_cell_count" "Lymphocytes" "Monocytes" "Neutrophilic_granulocyte" ...
 $ levels                 : chr  "Median (IQR)" "Mean ± SD" "Median (IQR)" "Mean ± SD" ...
 $ Healthy Control (N=239): chr  "6.5 (5.7 to 7.5)" "31.7 ± 7.0" "5.6 (4.8 to 6.5)" "60.3 ± 7.6" ...
 $ Case (N=362)           : chr  "6.3 (5.1 to 7.3)" "27.7 ± 7.8" "6.3 (5.5 to 7.5)" "62.3 ± 8.3" ...
 $ p                      : chr  ".022" "<.001" "<.001" ".004" ...
 - attr(*, "groups")= tibble [2 × 2] (S3: tbl_df/tbl/data.frame)
  ..$ group: Factor w/ 2 levels "Healthy Control",..: 1 2
  ..$ n    : chr [1:2] "(N=239)" "(N=362)"
 - attr(*, "yvars")= chr "group"
Code
## 9.2 Sanity check: replay the exact same cleaning pipeline on a fresh
## sample of data, to confirm the pipeline generalizes beyond the training rows.
set.seed(123)
new_data <- mat[sample(seq_len(nrow(mat)), 30), ]
processed_new <- process_new_data(stat_object = stat_obj, new_data = new_data, save_data = FALSE)
Applying missing value imputation...
Applying outlier handling to new data using training thresholds...
Applying normalization...
Code
cat("\nNew data processed. Dimensions:", nrow(processed_new), "x", ncol(processed_new), "\n")

New data processed. Dimensions: 30 x 25 
Code
## 9.3 Extract the final cleaned dataset for use outside icare if needed
final_clean <- ExtractCleanData(stat_obj)
cat("\nFinal clean dataset dimensions:", nrow(final_clean), "x", ncol(final_clean), "\n")

Final clean dataset dimensions: 601 x 25 
Code
write.csv(final_clean, file.path(OUTPUT_DIR, "clean_data_final.csv"), row.names = FALSE)

## 9.4 Save the Stat object itself — Module 2 (modeling) and Module 3
## (subtyping) both load this file directly via readRDS("stat_obj.rds"),
## so it is intentionally saved at the project root rather than inside
## OUTPUT_DIR.
saveRDS(stat_obj, file = "stat_obj.rds")

cat("\n========================================\n")

========================================
Code
cat("Module 1 (advanced) pipeline finished successfully!\n")
Module 1 (advanced) pipeline finished successfully!
Code
cat("All figures/tables saved under:", OUTPUT_DIR, "\n")
All figures/tables saved under: ./Module1_Advanced_Output 
Code
cat("========================================\n")
========================================