3  Module 1: Data Cleaning and Exploration — Quick Start

This chapter introduces the official Stat workflow: import an Excel cohort, create an object with CreateStatObject(), inspect variable types and data quality, clean and normalize the predictors, visualize group structure, test group differences, and create a publication-oriented Table 1. The saved stat_obj.rds retains the processed data and transformation state for Modules 2 and 3.

3.1 When to Use This Chapter

Use this chapter for a reproducible first pass through clinical or omics data. Confirm the inferred variable types and missing-data assumptions before accepting automated cleaning. Use the advanced chapter when the study requires comprehensive diagnostics, explicit outlier handling, differential-feature analysis, or manuscript figures.

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

3.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 1: Data Cleaning & Exploratory Analysis
# QUICK-START EDITION
# =============================================================================
# Audience : First-time users, or anyone who wants a working, minimal
#            analysis in a few minutes without touching every option.
# This script covers the core workflow only:
#   load data -> create a StatObject -> clean it -> look at one or two
#   summary plots -> run a group comparison -> get a baseline table.
#
# Looking for every option, publication-ready figures, or more diagnostics?
# See "01_module1_data_cleaning_ADVANCED.R" instead.
options(warn = -1)
# =============================================================================
rm(list = ls())
library(icare)
# ---------------------------------------------------------------------------

3.2.2 1 Load the data

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

Code
# 1. Load the data
# ---------------------------------------------------------------------------
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`
# ---------------------------------------------------------------------------

3.2.3 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
# ---------------------------------------------------------------------------
# This is the container icare uses to track your data through cleaning.
# group_col tells icare which column is the outcome you want to compare groups on.
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
# ---------------------------------------------------------------------------

3.2.4 3 Clean the data (one line per step, sensible defaults)

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

Code
# 3. Clean the data (one line per step, sensible defaults)
# ---------------------------------------------------------------------------
stat_obj <- stat_diagnose_variable_type(stat_obj)          # detect numeric vs. categorical
Diagnosed variable types:
Numeric variables: 23 
Categorical variables: 0 
Variables flagged for encoding: 0 
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'variable.types' slot updated.
Code
stat_obj <- stat_convert_variables(stat_obj)               # apply those types
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.
Cleaned data saved to: /tmp/RtmpeTEdn9/icare_output/StatObject/Data/clean_data.csv 
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'clean.data' slot updated.
Code
stat_obj <- stat_miss_processed(stat_obj, impute_method = "median_mode")  # fill missing values
Using simple median and mode imputation method...
Cleaned data saved to: /tmp/RtmpeTEdn9/icare_output/StatObject/Data/clean_data.csv 
Imputation completed successfully.
Updating 'Stat' object...
- 'clean.data' slot updated.
- 'process.info' slot updated.
Code
stat_obj <- stat_detect_and_mark_outliers(stat_obj, method = "iqr")       # flag outliers
Updating 'Stat' object...
- 'process.info' slot updated.
Code
stat_obj <- stat_handle_outliers(stat_obj, method = "impute", impute_value = "median") # fix them
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: 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: Mean_platelet_volume with median 
Imputed outliers in column: PCT with median 
Updating 'Stat' object...
- 'clean.data' slot updated.
- 'process.info' slot updated.
Code
stat_obj <- stat_onehot_encode(stat_obj)                   # encode categorical variables
Input object class: Stat 
Starting one-hot encoding on data...
Cleaned data saved to: /tmp/RtmpeTEdn9/icare_output/StatObject/Data/clean_data.csv 
Updating 'Stat' object...
The 'Stat' object has been updated with the following slots:
- 'clean.data' slot updated.
Code
stat_obj <- stat_normalize_process(stat_obj, method = "auto")            # normalize numeric variables
Cleaned data saved to: /tmp/RtmpeTEdn9/icare_output/StatObject/Data/scale_data.csv 
Updating 'Stat' object...
- 'scale.data' slot updated.
- 'process.info' slot updated.
Code
cat("Clean data ready:", nrow(stat_obj@clean.data), "rows x",
    ncol(stat_obj@clean.data), "columns\n")
Clean data ready: 601 rows x 23 columns
Code
# ---------------------------------------------------------------------------

3.2.5 4 Two quick summary plots

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

Code
# 4. Two quick summary plots
# ---------------------------------------------------------------------------
# A) How do the groups differ across a few key numeric variables?
numeric_vars <- names(stat_obj@clean.data)[sapply(stat_obj@clean.data, is.numeric)]
PlotGroupedDistribution(
  object    = stat_obj,
  features  = head(numeric_vars, 6),
  group_col = "group",
  test      = "wilcox.test",
  save_plot = TRUE
)
Generating grouped distribution plot...
Plot saved to: /home/runner/work/icare/icare/Figures/Stat/grouped_distribution.pdf 
Code
# B) Do the two groups separate overall (PCA)?
PlotPCA(
  object   = stat_obj,
  color_by = "group",
  ellipse  = TRUE,
  save_plot = TRUE
)
Generating PCA plot...
Plot saved to: /home/runner/work/icare/icare/Figures/Stat/PCA.pdf 
Code
# ---------------------------------------------------------------------------

3.2.6 5 Which features differ most between groups?

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

Code
# 5. Which features differ most between groups?
# ---------------------------------------------------------------------------
stat_obj <- stat_var_feature(stat_obj, p_threshold = 0.05)
Starting batch Wilcoxon test...
  Test data  :  601 samples x 23 features
  logFC data :  same as test data 
  logFC type : log2ratio 
Feature selection completed. Significant features: 18 
'var.result' slot updated.
Code
deg_result <- ExtractLastTestSig(stat_obj)
# ---------------------------------------------------------------------------

3.2.7 6 Baseline characteristics table (“Table 1”)

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
# 6. Baseline characteristics table ("Table 1")
# ---------------------------------------------------------------------------
stat_obj <- stat_gaze_analysis(stat_obj, save_word = TRUE)
Starting stat_gaze_analysis (safe version)...
Data prepared for gaze analysis. Number of rows: 601 Columns: 24 
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: /tmp/RtmpeTEdn9/icare_output/m1/baseline_tables/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 23 row(s) 
original dataset sample: 
Classes 'gaze', 'tibble' and 'data.frame':  23 obs. of  5 variables:
 $ name                   : chr  "White_blood_cell_count" "Lymphocytes" "Monocytes" "Neutrophilic_granulocyte" ...
 $ levels                 : chr  "Median (IQR)" "Median (IQR)" "Median (IQR)" "Median (IQR)" ...
 $ Healthy Control (N=239): chr  "6.6 (5.7 to 7.5)" "31.9 (27.1 to 36.8)" "5.6 (4.8 to 6.5)" "59.7 (54.9 to 65.5)" ...
 $ Case (N=362)           : chr  "6.3 (5.1 to 7.6)" "27.4 (22.2 to 32.4)" "6.3 (5.5 to 7.6)" "62.2 (57.3 to 68.5)" ...
 $ p                      : chr  ".056" "<.001" "<.001" "<.001" ...
 - 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
# ---------------------------------------------------------------------------

3.2.8 7 Save your work

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

Code
# 7. Save your work
# ---------------------------------------------------------------------------
# Module 2 (modeling) and Module 3 (subtyping) both can start by loading this file.
saveRDS(stat_obj, file = "stat_obj.rds")

cat("\nDone! stat_obj.rds saved — you're ready for Module 2 (modeling)",
    "or Module 3 (subtyping).\n")

Done! stat_obj.rds saved — you're ready for Module 2 (modeling) or Module 3 (subtyping).