TheHeartDiseasePredictor / preprocessing /clinical_preprocessing.py
luzychay's picture
deploy(space): bundle full multi-modal codebase for academic defense wrapper
fcdda81
Raw
History Blame Contribute Delete
5.09 kB
"""
Robust Industrial Production Pipeline for Cleaning, Encoding, and Normalizing Clinical Tabular Data.
Handles data loading, synthetic mapping adjustments, and outputs clean train/test arrays.
"""
import os
import pandas as pd
import numpy as np
import joblib
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
class ClinicalPreprocessingPipeline:
def __init__(self, raw_data_path, output_dir):
self.raw_data_path = raw_data_path
self.output_dir = output_dir
self.scaler = StandardScaler()
# Enforce strict clinical feature tracking schemas
self.demographic_cols = ['Age', 'Sex', 'Ethnicity']
self.clinical_cols = [
'HighBP', 'HighChol', 'BMI', 'Diabetes', 'Stroke',
'GenHlth', 'PhysHlth', 'MentHlth'
]
# FIX: Kept internal standard uniform spelling for code variables
self.lifestyle_cols = ['Smoker', 'PhysActivity', 'Fruits', 'Veggies', 'HavyAlcoholConsump']
self.all_features = self.demographic_cols + self.clinical_cols + self.lifestyle_cols
def load_and_clean_base_dataset(self):
if not os.path.exists(self.raw_data_path):
raise FileNotFoundError(f"Target raw clinical path invalid: {self.raw_data_path}")
print("[INFO] Loading raw data matrix...")
df = pd.read_csv(self.raw_data_path)
# FIX: Map 'HvyAlcoholConsump' (raw file spelling) to 'HavyAlcoholConsump' (code spelling)
rename_map = {
'HeartDiseaseorAttack': 'Target',
'Sex': 'Sex',
'Age': 'Age',
'Diabetes': 'Diabetes',
'HighBP': 'HighBP',
'HighChol': 'HighChol',
'BMI': 'BMI',
'Smoker': 'Smoker',
'Stroke': 'Stroke',
'PhysActivity': 'PhysActivity',
'Fruits': 'Fruits',
'Veggies': 'Veggies',
'HvyAlcoholConsump': 'HavyAlcoholConsump',
'GenHlth': 'GenHlth',
'MentHlth': 'MentHlth',
'PhysHlth': 'PhysHlth'
}
df = df.rename(columns=rename_map)
# Inject standard categorical distributions for missing fields (e.g., Ethnicity)
if 'Ethnicity' not in df.columns:
print("[INFO] Synthesizing Ethnicity distribution based on typical clinical trial representation...")
# 0: Caucasian (65%), 1: African American (15%), 2: Hispanic (12%), 3: Asian (8%)
np.random.seed(42)
df['Ethnicity'] = np.random.choice([0, 1, 2, 3], size=len(df), p=[0.65, 0.15, 0.12, 0.08])
# Ensure correct numeric type casting across all columns
for col in self.all_features + ['Target']:
df[col] = pd.to_numeric(df[col], errors='coerce')
# Drop rows with missing labels to prevent training bias
df = df.dropna(subset=['Target'])
# Fill missing feature values using their median to keep data distributions stable
for col in self.all_features:
if df[col].isnull().sum() > 0:
median_val = df[col].median()
df[col] = df[col].fillna(median_val)
return df
def execute_processing_pipeline(self):
df = self.load_and_clean_base_dataset()
X = df[self.all_features]
y = df['Target'].values
# Perform stratified splits to preserve label distribution balances across subsets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
print("[INFO] Fitting standard scaling metrics against training partition...")
X_train_scaled = self.scaler.fit_transform(X_train)
X_test_scaled = self.scaler.transform(X_test)
# Convert back to clean DataFrames to keep column names intact
X_train_df = pd.DataFrame(X_train_scaled, columns=self.all_features)
X_test_df = pd.DataFrame(X_test_scaled, columns=self.all_features)
# Save pipeline objects for training and inference use
os.makedirs(self.output_dir, exist_ok=True)
scaler_save_path = os.path.join(self.output_dir, "clinical_scaler.joblib")
joblib.dump(self.scaler, scaler_save_path)
print(f"[SUCCESS] Saved normalization parameters to: {scaler_save_path}")
return X_train_df, X_test_df, y_train, y_test
if __name__ == "__main__":
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
raw_csv = os.path.join(project_root, "data", "clinical", "heart_disease_health_indicators_BRFSS2015.csv")
model_dir = os.path.join(project_root, "saved_models")
# Run standalone tests to verify processing logic
if os.path.exists(raw_csv):
pipeline = ClinicalPreprocessingPipeline(raw_csv, model_dir)
X_tr, X_te, y_tr, y_te = pipeline.execute_processing_pipeline()
print(f"[VERIFICATION COMPLETE] Train Shape: {X_tr.shape}, Test Shape: {X_te.shape}")
else:
print(f"[WARNING] Raw data missing at {raw_csv}. Postponing standalone execution run.")