Spaces:
Sleeping
Sleeping
DIVYANSHI SINGH commited on
Commit ·
c4fc2e4
0
Parent(s):
DiaRisk Elite: Final LFS Production Build
Browse files- .gitattributes +3 -0
- .gitignore +26 -0
- LICENSE +21 -0
- README.md +59 -0
- app.py +427 -0
- models/elite_ensemble.pkl +3 -0
- models/logistic_regression.pkl +3 -0
- models/naive_bayes.pkl +3 -0
- models/random_forest.pkl +3 -0
- models/scaler.pkl +3 -0
- models/xgboost_model.pkl +3 -0
- models/xgboost_model_accuracy.pkl +3 -0
- outputs/confusion_matrix_elite.png +3 -0
- outputs/confusion_matrix_xgb.png +3 -0
- outputs/correlation_heatmap.png +3 -0
- outputs/diabetes_distribution.png +3 -0
- outputs/diabetes_rate_by_age.png +3 -0
- outputs/diabetes_rate_by_bmi.png +3 -0
- outputs/diabetes_rate_by_genhlth.png +3 -0
- outputs/feature_importance.png +3 -0
- outputs/feature_means_comparison.png +3 -0
- outputs/performance_metrics.csv +3 -0
- outputs/performance_metrics_elite.csv +3 -0
- outputs/roc_curves_all_models.png +3 -0
- outputs/roc_curves_elite_comparison.png +3 -0
- path_utils.py +33 -0
- pipeline/01_eda.py +87 -0
- pipeline/02_preprocessing.py +73 -0
- pipeline/03_train.py +99 -0
- pipeline/04_evaluate.py +104 -0
- requirements.txt +12 -0
- runtime.txt +1 -0
.gitattributes
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
.pytest_cache/
|
| 6 |
+
.tox/
|
| 7 |
+
.venv
|
| 8 |
+
venv/
|
| 9 |
+
ENV/
|
| 10 |
+
env/
|
| 11 |
+
|
| 12 |
+
# Streamlit
|
| 13 |
+
.streamlit/
|
| 14 |
+
|
| 15 |
+
# OS
|
| 16 |
+
.DS_Store
|
| 17 |
+
Thumbs.db
|
| 18 |
+
|
| 19 |
+
# Large Project Folders (Now optimized to <15MB each - INCLUDED)
|
| 20 |
+
# models/
|
| 21 |
+
data/
|
| 22 |
+
|
| 23 |
+
# IDEs
|
| 24 |
+
.vscode/
|
| 25 |
+
.idea/
|
| 26 |
+
.ipynb_checkpoints/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Divyanshi Singh
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: DiaRisk-Advanced Detection Engine
|
| 3 |
+
emoji: 🩺
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: teal
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# 🛡️ DiaRisk-Advanced Detection Engine
|
| 13 |
+
|
| 14 |
+
**DiaRisk** is a professional-grade clinical diagnostic system designed to identify Type 2 Diabetes risk patterns by transforming 253k+ CDC real-world health records into actionable medical insights.
|
| 15 |
+
|
| 16 |
+
[](https://huggingface.co/spaces/YOUR_USERNAME/diarisk-detection-engine)
|
| 17 |
+
[](https://opensource.org/licenses/MIT)
|
| 18 |
+
|
| 19 |
+
## 🩺 Project Intelligence & Mission
|
| 20 |
+
The core challenge in diabetes screening is balancing **Sensitivity (Recall)** vs. **Accuracy**. Missing a high-risk patient is clinically dangerous. DiaRisk addresses this by providing two optimized model tiers:
|
| 21 |
+
1. **Recall-Optimized XGBoost**: Designed for clinical safety, ensuring 79% of potential cases are flagged for screening.
|
| 22 |
+
2. **Elite Stacked Ensemble**: An advanced booster blending XGBoost, LightGBM, and CatBoost for a peak **86.4% Accuracy**.
|
| 23 |
+
|
| 24 |
+
## 📊 Key Clinical Insights
|
| 25 |
+
Our exploratory analysis of the CDC BRFSS dataset revealed critical risk drivers:
|
| 26 |
+
* **Obesity Impact**: Patients with BMI ≥ 30 show a **3x higher risk** of diabetes compared to those at a healthy weight.
|
| 27 |
+
* **Age Progression**: Risk doubles for every two age categories above 40, with a sharp spike after age 45.
|
| 28 |
+
* **General Health**: Self-assessed "Poor" or "Fair" health status is the strongest non-clinical predictor of current diabetic status.
|
| 29 |
+
|
| 30 |
+
## 🏆 Model Performance
|
| 31 |
+
| Model Tier | Accuracy | AUC-ROC | Recall (Sensitivity) | Focus |
|
| 32 |
+
|:---|:---|:---|:---|:---|
|
| 33 |
+
| **Elite Ensemble** | **86.4%** | **0.83** | 21% | Technical Precision |
|
| 34 |
+
| **XGBoost (Recall)** | 72.2% | **0.83** | **79.3%** | Clinical Safety |
|
| 35 |
+
|
| 36 |
+
## 🛠️ Technology Stack
|
| 37 |
+
- **Languages**: Python 3.10+
|
| 38 |
+
- **Boosters**: XGBoost, LightGBM, CatBoost
|
| 39 |
+
- **Ensemble**: Scikit-learn StackingClassifier
|
| 40 |
+
- **Dashboard**: Streamlit (Navy/Dark Clinical UI)
|
| 41 |
+
- **Visuals**: Plotly, Seaborn, Matplotlib
|
| 42 |
+
|
| 43 |
+
## 📂 Repository Structure
|
| 44 |
+
```text
|
| 45 |
+
├── data/ # Raw & Processed CDC Datasets
|
| 46 |
+
├── models/ # Serialized (.pkl) Elite Model Checkpoints
|
| 47 |
+
├── pipeline/ # End-to-end ML Scripts (EDA, Prep, Train, Eval)
|
| 48 |
+
├── outputs/ # Diagnostic Visualizations & Metrics
|
| 49 |
+
├── app.py # Main Dashboard Entry Point
|
| 50 |
+
└── path_utils.py # Global Path Management Utility
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## 🏗️ Installation & Usage
|
| 54 |
+
1. Clone the repository: `git clone https://github.com/Divyanshi018572/diarisk-detection-engine.git`
|
| 55 |
+
2. Install dependencies: `pip install -r requirements.txt`
|
| 56 |
+
3. Launch Dashboard: `streamlit run app.py`
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
**Build by Divyanshi Singh** | [GitHub](https://github.com/Divyanshi018572) | [LinkedIn](https://www.linkedin.com/in/divyanshi-singh-ds/)
|
app.py
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import joblib
|
| 5 |
+
import os
|
| 6 |
+
import plotly.graph_objects as go
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import seaborn as sns
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
# Path management
|
| 12 |
+
import path_utils
|
| 13 |
+
|
| 14 |
+
# --- PAGE CONFIG ---
|
| 15 |
+
st.set_page_config(
|
| 16 |
+
page_title="DiaRisk-Advanced Detection Engine",
|
| 17 |
+
page_icon="🩺",
|
| 18 |
+
layout="wide",
|
| 19 |
+
initial_sidebar_state="expanded"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
# --- CUSTOM NAVY DARK THEME CSS ---
|
| 23 |
+
st.markdown("""
|
| 24 |
+
<style>
|
| 25 |
+
/* Main Background & Text */
|
| 26 |
+
.main {
|
| 27 |
+
background-color: #0c121c;
|
| 28 |
+
color: #ffffff;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/* Global Text Color */
|
| 32 |
+
html, body, [class*="st-"] {
|
| 33 |
+
color: #e0e0e0 !important;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/* Sidebar styling */
|
| 37 |
+
[data-testid="stSidebar"] {
|
| 38 |
+
background-color: #16213e;
|
| 39 |
+
border-right: 1px solid #1f4068;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/* Headers */
|
| 43 |
+
h1, h2, h3, h4, h5, h6 {
|
| 44 |
+
color: #2ec4b6 !important;
|
| 45 |
+
font-family: 'Inter', sans-serif;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/* Input Fields Border & Background */
|
| 49 |
+
div[data-baseweb="input"], div[data-baseweb="select"], div[data-baseweb="textarea"] {
|
| 50 |
+
background-color: #1b263b !important;
|
| 51 |
+
border: 1px solid #334e68 !important;
|
| 52 |
+
border-radius: 8px !important;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* Checkbox & Radio Labels */
|
| 56 |
+
.stCheckbox label, .stRadio label {
|
| 57 |
+
color: #ffffff !important;
|
| 58 |
+
font-weight: 500 !important;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/* Button Styling */
|
| 62 |
+
.stButton>button {
|
| 63 |
+
width: 100%;
|
| 64 |
+
border-radius: 12px;
|
| 65 |
+
height: 3.5em;
|
| 66 |
+
background: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
|
| 67 |
+
color: white;
|
| 68 |
+
font-weight: bold;
|
| 69 |
+
border: none;
|
| 70 |
+
transition: all 0.3s ease;
|
| 71 |
+
box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.stButton>button:hover {
|
| 75 |
+
transform: translateY(-2px);
|
| 76 |
+
box-shadow: 0 6px 20px rgba(67, 97, 238, 0.5);
|
| 77 |
+
color: #ffffff;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/* Custom Risk Cards */
|
| 81 |
+
.risk-card {
|
| 82 |
+
padding: 25px;
|
| 83 |
+
border-radius: 15px;
|
| 84 |
+
background: rgba(22, 33, 62, 0.8);
|
| 85 |
+
border: 1px solid #334e68;
|
| 86 |
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
|
| 87 |
+
backdrop-filter: blur(10px);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.driver-item {
|
| 91 |
+
font-size: 1.05em;
|
| 92 |
+
padding: 10px;
|
| 93 |
+
margin-bottom: 5px;
|
| 94 |
+
background: rgba(27, 38, 59, 0.6);
|
| 95 |
+
border-radius: 8px;
|
| 96 |
+
border-left: 4px solid #4361ee;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/* Tabs styling */
|
| 100 |
+
.stTabs [data-baseweb="tab-list"] {
|
| 101 |
+
gap: 10px;
|
| 102 |
+
background-color: #0c121c;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.stTabs [data-baseweb="tab"] {
|
| 106 |
+
height: 50px;
|
| 107 |
+
white-space: pre-wrap;
|
| 108 |
+
background-color: #16213e;
|
| 109 |
+
border-radius: 8px 8px 0 0;
|
| 110 |
+
gap: 1px;
|
| 111 |
+
padding-top: 10px;
|
| 112 |
+
padding-bottom: 10px;
|
| 113 |
+
color: #ffffff !important;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.stTabs [data-baseweb="tab"]:hover {
|
| 117 |
+
background-color: #1f4068;
|
| 118 |
+
}
|
| 119 |
+
</style>
|
| 120 |
+
""", unsafe_allow_html=True)
|
| 121 |
+
|
| 122 |
+
# --- LOAD MODELS ---
|
| 123 |
+
@st.cache_resource
|
| 124 |
+
def load_assets():
|
| 125 |
+
models = {
|
| 126 |
+
'⭐ Elite Ensemble (Max Accuracy)': joblib.load(path_utils.get_models_path("elite_ensemble.pkl")),
|
| 127 |
+
'XGBoost (Recall Optimized)': joblib.load(path_utils.get_models_path("xgboost_model.pkl")),
|
| 128 |
+
'Logistic Regression': joblib.load(path_utils.get_models_path("logistic_regression.pkl")),
|
| 129 |
+
'Random Forest': joblib.load(path_utils.get_models_path("random_forest.pkl")),
|
| 130 |
+
'Naive Bayes': joblib.load(path_utils.get_models_path("naive_bayes.pkl"))
|
| 131 |
+
}
|
| 132 |
+
scaler = joblib.load(path_utils.get_models_path("scaler.pkl"))
|
| 133 |
+
return models, scaler
|
| 134 |
+
|
| 135 |
+
models, scaler = load_assets()
|
| 136 |
+
|
| 137 |
+
# --- SIDEBAR ---
|
| 138 |
+
st.sidebar.image("https://img.icons8.com/color/96/diabetes.png", width=100)
|
| 139 |
+
st.sidebar.title("App Intelligence")
|
| 140 |
+
selected_model_name = st.sidebar.selectbox("Predictive Engine", list(models.keys()))
|
| 141 |
+
threshold = st.sidebar.slider("Risk Cutoff Threshold", 0.3, 0.7, 0.5, 0.05)
|
| 142 |
+
st.sidebar.info("Adjust threshold to balance medical sensitivity vs precision.")
|
| 143 |
+
|
| 144 |
+
st.sidebar.divider()
|
| 145 |
+
st.sidebar.markdown("### 📊 Project Insights")
|
| 146 |
+
st.sidebar.write("""
|
| 147 |
+
This tool analyzes 22 health indicators from the CDC BRFSS dataset (253k rows) to quantify Diabetes risk.
|
| 148 |
+
""")
|
| 149 |
+
|
| 150 |
+
# --- TABS ---
|
| 151 |
+
tab0, tab1, tab2, tab3, tab4 = st.tabs([
|
| 152 |
+
"📄 Project Overview",
|
| 153 |
+
"🏥 Patient Risk Portal",
|
| 154 |
+
"📊 Population Explorer",
|
| 155 |
+
"📈 Model Calibration",
|
| 156 |
+
"⚖️ Disclaimer"
|
| 157 |
+
])
|
| 158 |
+
|
| 159 |
+
# --- TAB 0: PROJECT OVERVIEW ---
|
| 160 |
+
with tab0:
|
| 161 |
+
st.title("🛡️ DiaRisk-Advanced Detection Engine")
|
| 162 |
+
st.markdown("""
|
| 163 |
+
**DiaRisk** is a professional-grade clinical detection engine that transforms 253k+ CDC records into actionable health insights.
|
| 164 |
+
By leveraging a high-recall XGBoost and an Elite Stacked Ensemble, it identifies Type 2 Diabetes risk patterns with **86.4% accuracy**.
|
| 165 |
+
Designed for clinical pre-screening, it empowers early intervention through expert analytics and real-time risk stratification.
|
| 166 |
+
""")
|
| 167 |
+
|
| 168 |
+
col_ov1, col_ov2 = st.columns([1, 1])
|
| 169 |
+
with col_ov1:
|
| 170 |
+
st.subheader("The Problem Statement")
|
| 171 |
+
st.info("""
|
| 172 |
+
**Mission:** Identify high-risk individuals for Type 2 Diabetes using lifestyle and demographic indicators.
|
| 173 |
+
|
| 174 |
+
**Challenge:** How do we balance 'False Alarms' (Precision) vs. 'Missing Patients' (Recall)?
|
| 175 |
+
In clinical environments, **Missing a diabetic case is 10x more costly than a false alarm.**
|
| 176 |
+
""")
|
| 177 |
+
|
| 178 |
+
with col_ov2:
|
| 179 |
+
st.subheader("Performance Strategy")
|
| 180 |
+
st.write("""
|
| 181 |
+
We implemented two distinct model philosophies:
|
| 182 |
+
1. **Precision Elite (Stacked Ensemble):** Maximizes global Accuracy (86.4%).
|
| 183 |
+
2. **Clinical Heavyweight (Recall Opt XGB):** Maximizes detection of patients (79% Recall).
|
| 184 |
+
""")
|
| 185 |
+
|
| 186 |
+
st.divider()
|
| 187 |
+
|
| 188 |
+
st.subheader("🏆 The 'Best Overall' Model Analysis")
|
| 189 |
+
st.markdown("""
|
| 190 |
+
According to the clinical problem statement, the **XGBoost (Recall Optimized)** is the best overall performer.
|
| 191 |
+
|
| 192 |
+
| Model Tier | Metric Focus | Clinical Value |
|
| 193 |
+
|:---|:---|:---|
|
| 194 |
+
| **XGBoost (Recall Opt)** | **79% Sensitivity** | **High Utility** - Highest safety net for patient screening. |
|
| 195 |
+
| **Elite Ensemble** | **86.4% Accuracy** | **Technical Excellence** - Best for population-wide statistics. |
|
| 196 |
+
|
| 197 |
+
### **Why Recall Opt XGB Wins?**
|
| 198 |
+
In medical screening, our goal is to capture as many 'True Positive' risk profiles as possible. While the Elite Ensemble is more accurate overall, the Recall-Optimized model ensures more people are flagged for clinical HbA1c testing, directly supporting early intervention.
|
| 199 |
+
""")
|
| 200 |
+
st.caption("Intelligence Analysis built with 5Base Models + Stacked Ensemble Classifiers.")
|
| 201 |
+
|
| 202 |
+
# --- TAB 1: ASSESSMENT ---
|
| 203 |
+
with tab1:
|
| 204 |
+
st.title("🩺 Clinical Risk Assessment")
|
| 205 |
+
st.write("Complete the profile below for a real-time risk evaluation.")
|
| 206 |
+
|
| 207 |
+
col1, col2 = st.columns([1, 1])
|
| 208 |
+
|
| 209 |
+
with col1:
|
| 210 |
+
st.subheader("Demographics")
|
| 211 |
+
age_map = {
|
| 212 |
+
"18-24": 1, "25-29": 2, "30-34": 3, "35-39": 4, "40-44": 5,
|
| 213 |
+
"45-49": 6, "50-54": 7, "55-59": 8, "60-64": 9, "65-69": 10,
|
| 214 |
+
"70-74": 11, "75-79": 12, "80+": 13
|
| 215 |
+
}
|
| 216 |
+
age = st.selectbox("Current Age Range", list(age_map.keys()))
|
| 217 |
+
sex = st.radio("Biological Gender", ["Female", "Male"], horizontal=True)
|
| 218 |
+
|
| 219 |
+
st.subheader("Primary Metrics")
|
| 220 |
+
bmi = st.number_input("Body Mass Index (BMI)", 10.0, 100.0, 25.0)
|
| 221 |
+
# WHO Category feedback
|
| 222 |
+
if bmi < 18.5: st.warning(f"Classification: Underweight")
|
| 223 |
+
elif bmi < 25: st.success(f"Classification: Healthy Weight")
|
| 224 |
+
elif bmi < 30: st.info(f"Classification: Overweight")
|
| 225 |
+
else: st.error(f"Classification: Clinically Obese")
|
| 226 |
+
|
| 227 |
+
gen_hlth_map = {"Excellent": 1, "Very Good": 2, "Good": 3, "Fair": 4, "Poor": 5}
|
| 228 |
+
gen_hlth = st.radio("Self-Assessed General Health", list(gen_hlth_map.keys()), horizontal=True)
|
| 229 |
+
|
| 230 |
+
with col2:
|
| 231 |
+
st.subheader("Clinical History")
|
| 232 |
+
high_bp = st.checkbox("Diagnosed Hypertension (High BP)")
|
| 233 |
+
high_chol = st.checkbox("Diagnosed Dyslipidemia (High Chol)")
|
| 234 |
+
chol_check = st.checkbox("Cholesterol Screening (Past 5 Years)", value=True)
|
| 235 |
+
heart_disease = st.checkbox("History of Cardiac Events (Heart Disease)")
|
| 236 |
+
stroke = st.checkbox("History of Cerebrovascular Events (Stroke)")
|
| 237 |
+
|
| 238 |
+
st.subheader("Lifestyle Factors")
|
| 239 |
+
smoker = st.checkbox("Smoked 100+ Cigarettes in Lifetime")
|
| 240 |
+
phys_active = st.checkbox("Regular Physical Activity", value=True)
|
| 241 |
+
fruits = st.checkbox("Consume Fruits Daily", value=True)
|
| 242 |
+
veggies = st.checkbox("Consume Veggies Daily", value=True)
|
| 243 |
+
hvy_alcohol = st.checkbox("Heavy Alcohol Intake")
|
| 244 |
+
|
| 245 |
+
diff_walk = st.checkbox("Difficulty with Mobility (Climbing/Walking)")
|
| 246 |
+
ment_hlth = st.slider("Days of Poor Mental Health (Monthly)", 0, 30, 0)
|
| 247 |
+
phys_hlth = st.slider("Days of Poor Physical Health (Monthly)", 0, 30, 0)
|
| 248 |
+
|
| 249 |
+
# Required for features but less prominent
|
| 250 |
+
income_map = {"<$10k": 1, "$10k-15k": 2, "$15k-20k": 3, "$20k-25k": 4, "$25k-35k": 5, "$35k-50k": 6, "$50k-75k": 7, "$75k+": 8}
|
| 251 |
+
income = 5 # Defaulting
|
| 252 |
+
edu_map = {"No HS": 1, "Elem": 2, "Some HS": 3, "HS Grad": 4, "Some College": 5, "Coll Grad": 6}
|
| 253 |
+
edu = 4 # Defaulting
|
| 254 |
+
healthcare = 1 # Defaulting
|
| 255 |
+
doc_cost = 0 # Defaulting
|
| 256 |
+
|
| 257 |
+
# Prepare Data
|
| 258 |
+
input_data = {
|
| 259 |
+
'HighBP': int(high_bp), 'HighChol': int(high_chol), 'CholCheck': int(chol_check),
|
| 260 |
+
'BMI': bmi, 'Smoker': int(smoker), 'Stroke': int(stroke),
|
| 261 |
+
'HeartDiseaseorAttack': int(heart_disease), 'PhysActivity': int(phys_active),
|
| 262 |
+
'Fruits': int(fruits), 'Veggies': int(veggies), 'HvyAlcoholConsump': int(hvy_alcohol),
|
| 263 |
+
'AnyHealthcare': healthcare, 'NoDocbcCost': doc_cost,
|
| 264 |
+
'GenHlth': gen_hlth_map[gen_hlth], 'MentHlth': float(ment_hlth), 'PhysHlth': float(phys_hlth),
|
| 265 |
+
'DiffWalk': int(diff_walk), 'Sex': 1 if sex == "Male" else 0, 'Age': age_map[age],
|
| 266 |
+
'Education': edu, 'Income': income
|
| 267 |
+
}
|
| 268 |
+
input_data['BMI_OBESE'] = 1 if bmi >= 30 else 0
|
| 269 |
+
input_data['HIGH_RISK_COMBO'] = 1 if (high_bp and high_chol) else 0
|
| 270 |
+
phys_hlth_flag = 1 if phys_hlth > 14 else 0
|
| 271 |
+
input_data['POOR_HEALTH_SCORE'] = input_data['GenHlth'] + input_data['DiffWalk'] + phys_hlth_flag
|
| 272 |
+
|
| 273 |
+
feature_cols = ['HighBP', 'HighChol', 'CholCheck', 'BMI', 'Smoker', 'Stroke', 'HeartDiseaseorAttack', 'PhysActivity', 'Fruits', 'Veggies', 'HvyAlcoholConsump', 'AnyHealthcare', 'NoDocbcCost', 'GenHlth', 'MentHlth', 'PhysHlth', 'DiffWalk', 'Sex', 'Age', 'Education', 'Income', 'BMI_OBESE', 'HIGH_RISK_COMBO', 'POOR_HEALTH_SCORE']
|
| 274 |
+
input_df = pd.DataFrame([input_data])[feature_cols]
|
| 275 |
+
input_scaled = scaler.transform(input_df)
|
| 276 |
+
|
| 277 |
+
st.divider()
|
| 278 |
+
|
| 279 |
+
if st.button("RUN CLINICAL RISK ANALYSIS"):
|
| 280 |
+
model = models[selected_model_name]
|
| 281 |
+
prob = model.predict_proba(input_scaled)[0][1]
|
| 282 |
+
|
| 283 |
+
res_col1, res_col2 = st.columns([1, 1.3])
|
| 284 |
+
|
| 285 |
+
with res_col1:
|
| 286 |
+
fig = go.Figure(go.Indicator(
|
| 287 |
+
mode = "gauge+number",
|
| 288 |
+
value = prob * 100,
|
| 289 |
+
domain = {'x': [0, 1], 'y': [0, 1]},
|
| 290 |
+
title = {'text': "Risk Probability", 'font': {'size': 24, 'color': '#ffffff'}},
|
| 291 |
+
number = {'font': {'color': '#4cc9f0', 'size': 50}},
|
| 292 |
+
gauge = {
|
| 293 |
+
'axis': {'range': [None, 100], 'tickcolor': "#ffffff"},
|
| 294 |
+
'bar': {'color': "#4361ee"},
|
| 295 |
+
'bgcolor': "rgba(0,0,0,0)",
|
| 296 |
+
'steps': [
|
| 297 |
+
{'range': [0, 20], 'color': 'rgba(76, 201, 240, 0.2)'},
|
| 298 |
+
{'range': [20, 60], 'color': 'rgba(67, 97, 238, 0.2)'},
|
| 299 |
+
{'range': [60, 100], 'color': 'rgba(247, 37, 133, 0.2)'}],
|
| 300 |
+
}))
|
| 301 |
+
fig.update_layout(paper_bgcolor='rgba(0,0,0,0)', font={'color': "white"}, height=350, margin=dict(l=20, r=20, t=50, b=20))
|
| 302 |
+
st.plotly_chart(fig, use_column_width=True)
|
| 303 |
+
|
| 304 |
+
with res_col2:
|
| 305 |
+
st.markdown("### Risk Interpretation")
|
| 306 |
+
if prob < (threshold if selected_model_name == '⭐ Elite Ensemble (Max Accuracy)' else threshold): # Dynamic threshold logic if needed
|
| 307 |
+
st.success("#### PASS: LOW CLINICAL RISK")
|
| 308 |
+
message = "Your profile suggests low immediate risk. Continue regular checkups."
|
| 309 |
+
elif prob < 0.6:
|
| 310 |
+
st.warning("#### WARNING: ELEVATED RISK")
|
| 311 |
+
message = "Moderate markers detected. We recommend clinical consultation for blood glucose testing."
|
| 312 |
+
else:
|
| 313 |
+
st.error("#### ALERT: HIGH CLINICAL RISK")
|
| 314 |
+
message = "Significant risk factors identified. Consult a physician immediately for diagnostic screenings."
|
| 315 |
+
|
| 316 |
+
st.write(message)
|
| 317 |
+
|
| 318 |
+
st.markdown("#### Primary Stressors")
|
| 319 |
+
drivers = []
|
| 320 |
+
if high_bp: drivers.append("🔹 Hypertension (Strong clinical link)")
|
| 321 |
+
if bmi >= 30: drivers.append("🔹 Class 1+ Obesity (Metabolic driver)")
|
| 322 |
+
if gen_hlth_map[gen_hlth] >= 4: drivers.append("🔹 Self-Identified Poor General Health")
|
| 323 |
+
if age_map[age] >= 8: drivers.append("🔹 Age Interaction (Slowing metabolism)")
|
| 324 |
+
if not phys_active: drivers.append("🔹 Physical Inactivity")
|
| 325 |
+
|
| 326 |
+
for d in drivers[:4]:
|
| 327 |
+
st.markdown(f"<div class='driver-item'>{d}</div>", unsafe_allow_html=True)
|
| 328 |
+
|
| 329 |
+
# --- TAB 2: EXPLORER ---
|
| 330 |
+
with tab2:
|
| 331 |
+
st.title("📊 Population Risk Insights")
|
| 332 |
+
st.write("Visualizing the relationship between lifestyle and disease across 253k patient records.")
|
| 333 |
+
|
| 334 |
+
col_e1, col_e2 = st.columns(2)
|
| 335 |
+
with col_e1:
|
| 336 |
+
st.markdown("#### Risk by BMI Category")
|
| 337 |
+
img_bmi = path_utils.get_outputs_path("diabetes_rate_by_bmi.png")
|
| 338 |
+
if os.path.exists(img_bmi):
|
| 339 |
+
st.image(img_bmi, use_column_width=True)
|
| 340 |
+
st.info("""
|
| 341 |
+
**Clinical Insight:** Obesity (BMI ≥ 30) is the single most significant modifiable driver.
|
| 342 |
+
Data shows a **3x increase** in risk compared to the 'Healthy Weight' category.
|
| 343 |
+
""")
|
| 344 |
+
with col_e2:
|
| 345 |
+
st.markdown("#### Risk by Age Progression")
|
| 346 |
+
img_age = path_utils.get_outputs_path("diabetes_rate_by_age.png")
|
| 347 |
+
if os.path.exists(img_age):
|
| 348 |
+
st.image(img_age, use_column_width=True)
|
| 349 |
+
st.info("""
|
| 350 |
+
**Clinical Insight:** Vulnerability increases sharply after Age Category 7 (45+ years).
|
| 351 |
+
Risk doubles for every two age categories above 40.
|
| 352 |
+
""")
|
| 353 |
+
|
| 354 |
+
st.divider()
|
| 355 |
+
col_e3, col_e4 = st.columns(2)
|
| 356 |
+
with col_e3:
|
| 357 |
+
st.markdown("#### Feature Correlation Matrix")
|
| 358 |
+
img_corr = path_utils.get_outputs_path("correlation_heatmap.png")
|
| 359 |
+
if os.path.exists(img_corr):
|
| 360 |
+
st.image(img_corr, use_column_width=True)
|
| 361 |
+
st.info("**Key Drivers:** GenHlth, HighBP, BMI, and Age show the strongest positive correlation with current and pre-diabetic status.")
|
| 362 |
+
with col_e4:
|
| 363 |
+
st.markdown("#### Diabetic Clinical Median")
|
| 364 |
+
img_means = path_utils.get_outputs_path("feature_means_comparison.png")
|
| 365 |
+
if os.path.exists(img_means):
|
| 366 |
+
st.image(img_means, use_column_width=True)
|
| 367 |
+
st.info("**Pattern:** Patients with diabetes significantly exhibit co-occurring Hypertension and High Cholesterol ('High Risk Combo').")
|
| 368 |
+
|
| 369 |
+
# --- TAB 3: CALIBRATION ---
|
| 370 |
+
with tab3:
|
| 371 |
+
st.title("📈 Model Intelligence & Metrics")
|
| 372 |
+
st.write("Evaluating the predictive validity of the selected clinical model.")
|
| 373 |
+
|
| 374 |
+
st.subheader("Area Under Curve (AUC-ROC) Comparison")
|
| 375 |
+
img_roc = path_utils.get_outputs_path("roc_curves_elite_comparison.png") # Updated for Elite comparison
|
| 376 |
+
if os.path.exists(img_roc):
|
| 377 |
+
st.image(img_roc, use_column_width=True)
|
| 378 |
+
st.success("**Model Evolution:** The Elite Stacked Ensemble achieves over 86% Accuracy, outperforming baseline models by effectively blending XGB, LGBM, and CatBoost.")
|
| 379 |
+
|
| 380 |
+
col_m1, col_m2 = st.columns([1.5, 1])
|
| 381 |
+
with col_m1:
|
| 382 |
+
st.subheader("Global Feature Importance (XGBoost)")
|
| 383 |
+
img_imp = path_utils.get_outputs_path("feature_importance.png")
|
| 384 |
+
if os.path.exists(img_imp): st.image(img_imp, use_column_width=True)
|
| 385 |
+
|
| 386 |
+
with col_m2:
|
| 387 |
+
st.subheader("Comparative Metrics (Elite Stack)")
|
| 388 |
+
metrics_file = path_utils.get_outputs_path("performance_metrics_elite.csv") # Updated for Elite metrics
|
| 389 |
+
if os.path.exists(metrics_file):
|
| 390 |
+
metrics_df = pd.read_csv(metrics_file)
|
| 391 |
+
st.dataframe(metrics_df.style.background_gradient(cmap='Blues', subset=['Accuracy']))
|
| 392 |
+
|
| 393 |
+
st.subheader("Confusion Matrix (Elite Champion)")
|
| 394 |
+
img_cm = path_utils.get_outputs_path("confusion_matrix_elite.png") # Updated for Elite CM
|
| 395 |
+
if os.path.exists(img_cm):
|
| 396 |
+
st.image(img_cm, use_column_width=True)
|
| 397 |
+
st.info("**Clinical Utility:** The Elite Model focuses on overall predictive accuracy, identifying the majority of non-diabetic cases with higher precision than the Recall-tuned XGBoost.")
|
| 398 |
+
|
| 399 |
+
# --- TAB 4: DISCLAIMER ---
|
| 400 |
+
with tab4:
|
| 401 |
+
st.title("⚖️ Legal & Clinical Disclaimer")
|
| 402 |
+
st.warning("PLEASE READ CAREFULLY")
|
| 403 |
+
st.markdown("""
|
| 404 |
+
### 1. EDUCATIONAL PURPOSE
|
| 405 |
+
This application is designed as a **technical showcase** of machine learning capabilities in the healthcare domain. It is **NOT** a medical diagnostic tool and should not be used as a substitute for professional medical advice.
|
| 406 |
+
|
| 407 |
+
### 2. PREDICTIVE NATURE
|
| 408 |
+
Machine Learning models predict based on patterns found in historical population data (CDC BRFSS 2015). A predicted probability is a statistical estimate, not a clinical diagnosis.
|
| 409 |
+
|
| 410 |
+
### 3. ACTIONABLE ADVICE
|
| 411 |
+
If this tool flags you as "High Risk," it serves as a prompt for you to **consult a licensed physician** for blood tests such as HbA1c or Fasting Plasma Glucose.
|
| 412 |
+
|
| 413 |
+
### 4. DATA PRIVACY
|
| 414 |
+
All data processed in this session is volatile and cleared upon page refresh. No clinical data is stored in any database.
|
| 415 |
+
""")
|
| 416 |
+
st.info("Dataset: CDC Diabetes Health Indicators | Model: Elite Stacked Ensemble")
|
| 417 |
+
|
| 418 |
+
# --- FOOTER ---
|
| 419 |
+
st.markdown("""
|
| 420 |
+
<br><hr>
|
| 421 |
+
<center>
|
| 422 |
+
<p style='color: #a0a0a0;'>Diabetes Risk Prediction System | Built by <b>Divyanshi Singh</b></p>
|
| 423 |
+
<a href='https://github.com/Divyanshi018572' target='_blank'><img src='https://img.icons8.com/fluent/32/000000/github.png' width='25'/></a>
|
| 424 |
+
<a href='https://www.linkedin.com/in/divyanshi-singh-ds/' target='_blank'><img src='https://img.icons8.com/fluent/32/000000/linkedin.png' width='25'/></a>
|
| 425 |
+
<p style='color: #606060; font-size: 0.8em;'>© 2026 Professional Risk Engine | Data Science Portfolio</p>
|
| 426 |
+
</center>
|
| 427 |
+
""", unsafe_allow_html=True)
|
models/elite_ensemble.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:faff1e121de704459e255cd351a32fb1acbae867c827e0e375bae0b94cd54389
|
| 3 |
+
size 3803481
|
models/logistic_regression.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f8a31415ecef2987564624e915550d6cb8b486357add292135f281da6c15c0e0
|
| 3 |
+
size 1228
|
models/naive_bayes.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ddf65a38aaf1732c794ad501f088429f28bed657a9fcbfe0bbfff54ddc59b8df
|
| 3 |
+
size 1766
|
models/random_forest.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3437abcc72d34da4d06c8a716b5c7c6b623847ccc53b642100cc62933bf24818
|
| 3 |
+
size 3940691
|
models/scaler.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d19c14a0a476bc8917bccdb4dd5bc919b6419198b58aade784e7b7e7276c5550
|
| 3 |
+
size 1863
|
models/xgboost_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:08fc16cd817332f6b392d24944dfb9b9321e112cf07c898fd05e0f0d30deb915
|
| 3 |
+
size 155909
|
models/xgboost_model_accuracy.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3f3d55301aba0f6976f493a85dadb39c5a6da2c7bc9b29c0968af20b61394a7c
|
| 3 |
+
size 538670
|
outputs/confusion_matrix_elite.png
ADDED
|
Git LFS Details
|
outputs/confusion_matrix_xgb.png
ADDED
|
Git LFS Details
|
outputs/correlation_heatmap.png
ADDED
|
Git LFS Details
|
outputs/diabetes_distribution.png
ADDED
|
Git LFS Details
|
outputs/diabetes_rate_by_age.png
ADDED
|
Git LFS Details
|
outputs/diabetes_rate_by_bmi.png
ADDED
|
Git LFS Details
|
outputs/diabetes_rate_by_genhlth.png
ADDED
|
Git LFS Details
|
outputs/feature_importance.png
ADDED
|
Git LFS Details
|
outputs/feature_means_comparison.png
ADDED
|
Git LFS Details
|
outputs/performance_metrics.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:359ddafbe7c108e040f53f6c1571d23ad51ca24114af8cb8a5aab1d699beeade
|
| 3 |
+
size 482
|
outputs/performance_metrics_elite.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dca93920962c552b8f00947c7bcf6231fd3c94011a1dce433a3f4992d6db707b
|
| 3 |
+
size 520
|
outputs/roc_curves_all_models.png
ADDED
|
Git LFS Details
|
outputs/roc_curves_elite_comparison.png
ADDED
|
Git LFS Details
|
path_utils.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
def get_project_root():
|
| 4 |
+
"""Returns the root path of the project."""
|
| 5 |
+
return os.path.dirname(os.path.abspath(__file__))
|
| 6 |
+
|
| 7 |
+
def get_data_path(subdir="raw", filename=None):
|
| 8 |
+
"""Returns the path to the data directory."""
|
| 9 |
+
path = os.path.join(get_project_root(), "data", subdir)
|
| 10 |
+
if filename:
|
| 11 |
+
path = os.path.join(path, filename)
|
| 12 |
+
return path
|
| 13 |
+
|
| 14 |
+
def get_models_path(filename=None):
|
| 15 |
+
"""Returns the path to the models directory."""
|
| 16 |
+
path = os.path.join(get_project_root(), "models")
|
| 17 |
+
if filename:
|
| 18 |
+
path = os.path.join(path, filename)
|
| 19 |
+
return path
|
| 20 |
+
|
| 21 |
+
def get_outputs_path(filename=None):
|
| 22 |
+
"""Returns the path to the outputs directory."""
|
| 23 |
+
path = os.path.join(get_project_root(), "outputs")
|
| 24 |
+
if filename:
|
| 25 |
+
path = os.path.join(path, filename)
|
| 26 |
+
return path
|
| 27 |
+
|
| 28 |
+
def get_pipeline_path(filename=None):
|
| 29 |
+
"""Returns the path to the pipeline directory."""
|
| 30 |
+
path = os.path.join(get_project_root(), "pipeline")
|
| 31 |
+
if filename:
|
| 32 |
+
path = os.path.join(path, filename)
|
| 33 |
+
return path
|
pipeline/01_eda.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
import seaborn as sns
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
# Add project root to sys.path
|
| 9 |
+
# This assumes the script is run from the project root or pipeline directory
|
| 10 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 11 |
+
import path_utils
|
| 12 |
+
|
| 13 |
+
def run_eda():
|
| 14 |
+
"""Runs data exploration and saves plots to outputs folder."""
|
| 15 |
+
# 1. Load Data
|
| 16 |
+
csv_file = path_utils.get_data_path("raw", "diabetes_binary_health_indicators_BRFSS2015.csv")
|
| 17 |
+
df = pd.read_csv(csv_file)
|
| 18 |
+
print(f"Dataset Loaded: {df.shape[0]} rows, {df.shape[1]} columns")
|
| 19 |
+
print(f"Null Values: {df.isnull().sum().sum()}")
|
| 20 |
+
|
| 21 |
+
# Ensure outputs directory exists
|
| 22 |
+
os.makedirs(path_utils.get_outputs_path(), exist_ok=True)
|
| 23 |
+
|
| 24 |
+
# 2. Target Distribution
|
| 25 |
+
plt.figure(figsize=(8, 6))
|
| 26 |
+
sns.countplot(x='Diabetes_binary', data=df, palette='viridis')
|
| 27 |
+
plt.title('Diabetes Distribution (0=No, 1=Yes/Pre)')
|
| 28 |
+
plt.savefig(path_utils.get_outputs_path("diabetes_distribution.png"))
|
| 29 |
+
plt.close()
|
| 30 |
+
|
| 31 |
+
# 3. Diabetes Rate by Age Category
|
| 32 |
+
plt.figure(figsize=(10, 6))
|
| 33 |
+
sns.lineplot(x='Age', y='Diabetes_binary', data=df, marker='o', color='blue')
|
| 34 |
+
plt.title('Diabetes Rate by Age category (1=18-24, 13=80+)')
|
| 35 |
+
plt.ylabel('Risk Probability')
|
| 36 |
+
plt.grid(True)
|
| 37 |
+
plt.savefig(path_utils.get_outputs_path("diabetes_rate_by_age.png"))
|
| 38 |
+
plt.close()
|
| 39 |
+
|
| 40 |
+
# 4. Diabetes Rate by BMI Bins
|
| 41 |
+
# (Underweight <18.5, Normal 18.5–25, Overweight 25–30, Obese 30+)
|
| 42 |
+
df['BMI_Bins'] = pd.cut(df['BMI'], bins=[0, 18.5, 25, 30, 100],
|
| 43 |
+
labels=['Underweight', 'Normal', 'Overweight', 'Obese'])
|
| 44 |
+
plt.figure(figsize=(10, 6))
|
| 45 |
+
sns.barplot(x='BMI_Bins', y='Diabetes_binary', data=df, palette='coolwarm')
|
| 46 |
+
plt.title('Diabetes Rate by BMI Category')
|
| 47 |
+
plt.ylabel('Risk Probability')
|
| 48 |
+
plt.savefig(path_utils.get_outputs_path("diabetes_rate_by_bmi.png"))
|
| 49 |
+
plt.close()
|
| 50 |
+
|
| 51 |
+
# 5. Diabetes Rate by GenHlth (General Health Rating 1-5)
|
| 52 |
+
plt.figure(figsize=(10, 6))
|
| 53 |
+
sns.barplot(x='GenHlth', y='Diabetes_binary', data=df, palette='OrRd')
|
| 54 |
+
plt.title('Diabetes Rate by General Health Rating (1=Excellent, 5=Poor)')
|
| 55 |
+
plt.ylabel('Risk Probability')
|
| 56 |
+
plt.savefig(path_utils.get_outputs_path("diabetes_rate_by_genhlth.png"))
|
| 57 |
+
plt.close()
|
| 58 |
+
|
| 59 |
+
# 6. Correlation Heatmap (Top 10 features correlate with Diabetes)
|
| 60 |
+
plt.figure(figsize=(12, 10))
|
| 61 |
+
# Filter only numeric columns for correlation
|
| 62 |
+
numeric_df = df.select_dtypes(include=[np.number])
|
| 63 |
+
corr_matrix = numeric_df.corr()
|
| 64 |
+
# Pull Top Correlations with Target
|
| 65 |
+
top_corr = corr_matrix['Diabetes_binary'].sort_values(ascending=False).head(10).index
|
| 66 |
+
sns.heatmap(df[top_corr].corr(), annot=True, cmap='RdBu_r', center=0)
|
| 67 |
+
plt.title('Top 10 Feature Correlation Heatmap')
|
| 68 |
+
plt.savefig(path_utils.get_outputs_path("correlation_heatmap.png"))
|
| 69 |
+
plt.close()
|
| 70 |
+
|
| 71 |
+
# 7. Comparison: Mean values for Diabetic vs Non-Diabetic
|
| 72 |
+
comp_features = ['HighBP', 'HighChol', 'BMI', 'Age', 'Smoker', 'HeartDiseaseorAttack', 'PhysActivity']
|
| 73 |
+
comp_df = df.groupby('Diabetes_binary')[comp_features].mean().reset_index()
|
| 74 |
+
# Melt for plotting
|
| 75 |
+
comp_melted = comp_df.melt(id_vars='Diabetes_binary', var_name='Feature', value_name='Mean Value')
|
| 76 |
+
|
| 77 |
+
plt.figure(figsize=(12, 6))
|
| 78 |
+
sns.barplot(x='Feature', y='Mean Value', hue='Diabetes_binary', data=comp_melted, palette='muted')
|
| 79 |
+
plt.title('Mean Feature Values: Diabetic (1) vs Non-Diabetic (0)')
|
| 80 |
+
plt.xticks(rotation=45)
|
| 81 |
+
plt.savefig(path_utils.get_outputs_path("feature_means_comparison.png"))
|
| 82 |
+
plt.close()
|
| 83 |
+
|
| 84 |
+
print("EDA Visualizations saved to 'outputs/'")
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
run_eda()
|
pipeline/02_preprocessing.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.model_selection import train_test_split
|
| 4 |
+
from sklearn.preprocessing import StandardScaler
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
import joblib
|
| 8 |
+
|
| 9 |
+
# Add project root to sys.path
|
| 10 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 11 |
+
import path_utils
|
| 12 |
+
|
| 13 |
+
def run_preprocessing():
|
| 14 |
+
"""Performs feature engineering, scaling, and splitting."""
|
| 15 |
+
# 1. Load Data
|
| 16 |
+
csv_file = path_utils.get_data_path("raw", "diabetes_binary_health_indicators_BRFSS2015.csv")
|
| 17 |
+
df = pd.read_csv(csv_file)
|
| 18 |
+
print(f"Initial Dataset: {df.shape[0]} rows, {df.shape[1]} columns")
|
| 19 |
+
|
| 20 |
+
# 2. Clinical Feature Engineering
|
| 21 |
+
print("Engineering Clinical Features...")
|
| 22 |
+
# BMI_OBESE = 1 if BMI >= 30 else 0
|
| 23 |
+
df['BMI_OBESE'] = (df['BMI'] >= 30).astype(int)
|
| 24 |
+
|
| 25 |
+
# HIGH_RISK_COMBO = 1 if HighBP == 1 AND HighChol == 1 else 0
|
| 26 |
+
df['HIGH_RISK_COMBO'] = ((df['HighBP'] == 1) & (df['HighChol'] == 1)).astype(int)
|
| 27 |
+
|
| 28 |
+
# POOR_HEALTH_SCORE = GenHlth + DiffWalk + PhysHlth_flag
|
| 29 |
+
# where PhysHlth_flag = 1 if PhysHlth > 14
|
| 30 |
+
df['PhysHlth_flag'] = (df['PhysHlth'] > 14).astype(int)
|
| 31 |
+
df['POOR_HEALTH_SCORE'] = df['GenHlth'] + df['DiffWalk'] + df['PhysHlth_flag']
|
| 32 |
+
|
| 33 |
+
# Drop intermediate flag
|
| 34 |
+
df.drop(columns=['PhysHlth_flag'], inplace=True)
|
| 35 |
+
|
| 36 |
+
print(f"Features Engineered. Total columns: {df.shape[1]}")
|
| 37 |
+
|
| 38 |
+
# 3. Features and Target
|
| 39 |
+
X = df.drop(columns=['Diabetes_binary'])
|
| 40 |
+
y = df['Diabetes_binary']
|
| 41 |
+
|
| 42 |
+
# 4. Train-Test Split (80/20, stratified)
|
| 43 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
| 44 |
+
X, y, test_size=0.2, random_state=42, stratify=y
|
| 45 |
+
)
|
| 46 |
+
print(f"Splits Created: Train={X_train.shape[0]}, Test={X_test.shape[0]}")
|
| 47 |
+
|
| 48 |
+
# 5. Scaling
|
| 49 |
+
print("Scaling Features...")
|
| 50 |
+
scaler = StandardScaler()
|
| 51 |
+
X_train_scaled = scaler.fit_transform(X_train)
|
| 52 |
+
X_test_scaled = scaler.transform(X_test)
|
| 53 |
+
|
| 54 |
+
# Save Scaler for later use in app.py
|
| 55 |
+
os.makedirs(path_utils.get_models_path(), exist_ok=True)
|
| 56 |
+
joblib.dump(scaler, path_utils.get_models_path("scaler.pkl"))
|
| 57 |
+
|
| 58 |
+
# 6. Save Processed Data
|
| 59 |
+
os.makedirs(path_utils.get_data_path("processed"), exist_ok=True)
|
| 60 |
+
|
| 61 |
+
# Convert scaled back to DataFrame to preserve feature names for training script
|
| 62 |
+
X_train_final = pd.DataFrame(X_train_scaled, columns=X.columns)
|
| 63 |
+
X_test_final = pd.DataFrame(X_test_scaled, columns=X.columns)
|
| 64 |
+
|
| 65 |
+
X_train_final.to_csv(path_utils.get_data_path("processed", "X_train.csv"), index=False)
|
| 66 |
+
X_test_final.to_csv(path_utils.get_data_path("processed", "X_test.csv"), index=False)
|
| 67 |
+
y_train.to_csv(path_utils.get_data_path("processed", "y_train.csv"), index=False)
|
| 68 |
+
y_test.to_csv(path_utils.get_data_path("processed", "y_test.csv"), index=False)
|
| 69 |
+
|
| 70 |
+
print("Preprocessing Complete. Data and Scaler saved.")
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
run_preprocessing()
|
pipeline/03_train.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import joblib
|
| 6 |
+
from sklearn.linear_model import LogisticRegression
|
| 7 |
+
from sklearn.naive_bayes import GaussianNB
|
| 8 |
+
from sklearn.ensemble import RandomForestClassifier, StackingClassifier
|
| 9 |
+
from xgboost import XGBClassifier
|
| 10 |
+
from lightgbm import LGBMClassifier
|
| 11 |
+
from catboost import CatBoostClassifier
|
| 12 |
+
from sklearn.model_selection import train_test_split
|
| 13 |
+
|
| 14 |
+
# Add project root to sys.path
|
| 15 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 16 |
+
import path_utils
|
| 17 |
+
|
| 18 |
+
def run_training():
|
| 19 |
+
"""Trains baseline models and a champion Elite Stacked Ensemble for maximum accuracy."""
|
| 20 |
+
# 1. Load Processed Data
|
| 21 |
+
X_train = pd.read_csv(path_utils.get_data_path("processed", "X_train.csv"))
|
| 22 |
+
y_train = pd.read_csv(path_utils.get_data_path("processed", "y_train.csv")).values.ravel()
|
| 23 |
+
|
| 24 |
+
print(f"HF-Optimized Training on {X_train.shape[0]} samples with {X_train.shape[1]} features.")
|
| 25 |
+
|
| 26 |
+
# 2. Train Optimized Baselines
|
| 27 |
+
print("Training Optimized Baselines (Pruned to <10MB)...")
|
| 28 |
+
|
| 29 |
+
# Logistic Regression (Tiny)
|
| 30 |
+
lr = LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42).fit(X_train, y_train)
|
| 31 |
+
joblib.dump(lr, path_utils.get_models_path("logistic_regression.pkl"), compress=3)
|
| 32 |
+
|
| 33 |
+
# Naive Bayes (Tiny)
|
| 34 |
+
nb = GaussianNB(priors=[0.86, 0.14], var_smoothing=1e-8).fit(X_train, y_train)
|
| 35 |
+
joblib.dump(nb, path_utils.get_models_path("naive_bayes.pkl"), compress=3)
|
| 36 |
+
|
| 37 |
+
# 🛑 KNN Removed: Too large for standard HF push (>60MB)
|
| 38 |
+
if os.path.exists(path_utils.get_models_path("knn.pkl")):
|
| 39 |
+
os.remove(path_utils.get_models_path("knn.pkl"))
|
| 40 |
+
|
| 41 |
+
# Pruning Random Forest to < 10MB (max_depth=10, n_estimators=75)
|
| 42 |
+
rf = RandomForestClassifier(
|
| 43 |
+
n_estimators=75, max_depth=10, class_weight='balanced',
|
| 44 |
+
random_state=42, n_jobs=-1
|
| 45 |
+
).fit(X_train, y_train)
|
| 46 |
+
joblib.dump(rf, path_utils.get_models_path("random_forest.pkl"), compress=3)
|
| 47 |
+
|
| 48 |
+
# 3. Define "Elite" Base Learners (Pruned for 10MB threshold)
|
| 49 |
+
print("Initializing Elite Ensemble Base Learners (Pruned for Deploy)...")
|
| 50 |
+
|
| 51 |
+
xgb_elite = XGBClassifier(
|
| 52 |
+
n_estimators=100, max_depth=6, learning_rate=0.1,
|
| 53 |
+
random_state=42, use_label_encoder=False, eval_metric='logloss'
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
lgb_elite = LGBMClassifier(
|
| 57 |
+
n_estimators=100, num_leaves=31, learning_rate=0.1,
|
| 58 |
+
random_state=42, verbose=-1
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
cat_elite = CatBoostClassifier(
|
| 62 |
+
iterations=100, depth=6, learning_rate=0.1,
|
| 63 |
+
random_seed=42, verbose=0, allow_writing_files=False
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
rf_elite = RandomForestClassifier(
|
| 67 |
+
n_estimators=80, max_depth=10, random_state=42, n_jobs=-1
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
# 4. Create Stacked Ensemble
|
| 71 |
+
print("Training Elite Stacked Ensemble (XGB + LGBM + Cat + RF)...")
|
| 72 |
+
base_learners = [
|
| 73 |
+
('xgb', xgb_elite),
|
| 74 |
+
('lgbm', lgb_elite),
|
| 75 |
+
('cat', cat_elite),
|
| 76 |
+
('rf', rf_elite)
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
stack = StackingClassifier(
|
| 80 |
+
estimators=base_learners,
|
| 81 |
+
final_estimator=LogisticRegression(),
|
| 82 |
+
n_jobs=-1
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
stack.fit(X_train, y_train)
|
| 86 |
+
# Save with Level 3 Compression (Ensuring < 10MB)
|
| 87 |
+
joblib.dump(stack, path_utils.get_models_path("elite_ensemble.pkl"), compress=3)
|
| 88 |
+
|
| 89 |
+
# 5. XGBoost (Recall Optimized) - Tiny
|
| 90 |
+
xgb_recall = XGBClassifier(
|
| 91 |
+
n_estimators=100, max_depth=6, learning_rate=0.1,
|
| 92 |
+
scale_pos_weight=6.14, random_state=42, eval_metric='logloss'
|
| 93 |
+
).fit(X_train, y_train)
|
| 94 |
+
joblib.dump(xgb_recall, path_utils.get_models_path("xgboost_model.pkl"), compress=3)
|
| 95 |
+
|
| 96 |
+
print("10MB Threshold Training Complete. All models saved successfully.")
|
| 97 |
+
|
| 98 |
+
if __name__ == "__main__":
|
| 99 |
+
run_training()
|
pipeline/04_evaluate.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
import seaborn as sns
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
import joblib
|
| 8 |
+
from sklearn.metrics import accuracy_score, roc_auc_score, recall_score, f1_score, roc_curve, confusion_matrix
|
| 9 |
+
|
| 10 |
+
# Add project root to sys.path
|
| 11 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 12 |
+
import path_utils
|
| 13 |
+
|
| 14 |
+
def run_evaluation():
|
| 15 |
+
"""Evaluates all models including the Elite Stacked Ensemble."""
|
| 16 |
+
# 1. Load Data
|
| 17 |
+
X_test = pd.read_csv(path_utils.get_data_path("processed", "X_test.csv"))
|
| 18 |
+
y_test = pd.read_csv(path_utils.get_data_path("processed", "y_test.csv")).values.ravel()
|
| 19 |
+
|
| 20 |
+
# 2. Identify Models and Load Them
|
| 21 |
+
# Add 'Elite Ensemble' and 'XGBoost (Accuracy Opt)' to comparison
|
| 22 |
+
model_paths = {
|
| 23 |
+
'Logistic Regression': "logistic_regression.pkl",
|
| 24 |
+
'Naive Bayes': "naive_bayes.pkl",
|
| 25 |
+
'Random Forest': "random_forest.pkl",
|
| 26 |
+
'Elite Ensemble (Max Accuracy)': "elite_ensemble.pkl",
|
| 27 |
+
'XGBoost (Recall Opt)': "xgboost_model.pkl"
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
models = {}
|
| 31 |
+
for name, filename in model_paths.items():
|
| 32 |
+
path = path_utils.get_models_path(filename)
|
| 33 |
+
if os.path.exists(path):
|
| 34 |
+
models[name] = joblib.load(path)
|
| 35 |
+
else:
|
| 36 |
+
print(f"Warning: {filename} not found.")
|
| 37 |
+
|
| 38 |
+
# 3. Store Results
|
| 39 |
+
results = []
|
| 40 |
+
plt.figure(figsize=(12, 10))
|
| 41 |
+
|
| 42 |
+
for name, model in models.items():
|
| 43 |
+
print(f"Evaluating {name}...")
|
| 44 |
+
y_pred = model.predict(X_test)
|
| 45 |
+
|
| 46 |
+
# Get Probabilities for ROC curve
|
| 47 |
+
if hasattr(model, "predict_proba"):
|
| 48 |
+
y_prob = model.predict_proba(X_test)[:, 1]
|
| 49 |
+
else:
|
| 50 |
+
# Stacking classifier has predict_proba
|
| 51 |
+
y_prob = model.decision_function(X_test)
|
| 52 |
+
|
| 53 |
+
# Metrics
|
| 54 |
+
acc = accuracy_score(y_test, y_pred)
|
| 55 |
+
auc = roc_auc_score(y_test, y_prob)
|
| 56 |
+
rec = recall_score(y_test, y_pred)
|
| 57 |
+
f1 = f1_score(y_test, y_pred)
|
| 58 |
+
|
| 59 |
+
results.append({
|
| 60 |
+
'Model': name,
|
| 61 |
+
'Accuracy': acc,
|
| 62 |
+
'AUC-ROC': auc,
|
| 63 |
+
'Recall': rec,
|
| 64 |
+
'F1-Score': f1
|
| 65 |
+
})
|
| 66 |
+
|
| 67 |
+
# ROC Plotting
|
| 68 |
+
fpr, tpr, _ = roc_curve(y_test, y_prob)
|
| 69 |
+
plt.plot(fpr, tpr, label=f"{name} (AUC={auc:.2f})")
|
| 70 |
+
|
| 71 |
+
# Finalize ROC Plot
|
| 72 |
+
plt.plot([0, 1], [0, 1], 'k--', alpha=0.5)
|
| 73 |
+
plt.xlabel('False Positive Rate')
|
| 74 |
+
plt.ylabel('True Positive Rate')
|
| 75 |
+
plt.title('ROC Curves: Comparing Baseline vs Elite Ensemble')
|
| 76 |
+
plt.legend(loc='lower right')
|
| 77 |
+
plt.grid(alpha=0.3)
|
| 78 |
+
plt.savefig(path_utils.get_outputs_path("roc_curves_elite_comparison.png"))
|
| 79 |
+
plt.close()
|
| 80 |
+
|
| 81 |
+
# 4. Save Performance Table
|
| 82 |
+
results_df = pd.DataFrame(results).sort_values(by='Accuracy', ascending=False)
|
| 83 |
+
print("\nElite Model Performance Comparison:")
|
| 84 |
+
print(results_df)
|
| 85 |
+
results_df.to_csv(path_utils.get_outputs_path("performance_metrics_elite.csv"), index=False)
|
| 86 |
+
|
| 87 |
+
# 5. Elite Confusion Matrix
|
| 88 |
+
if 'Elite Ensemble (Max Accuracy)' in models:
|
| 89 |
+
best_model = models['Elite Ensemble (Max Accuracy)']
|
| 90 |
+
y_pred_elite = best_model.predict(X_test)
|
| 91 |
+
cm = confusion_matrix(y_test, y_pred_elite)
|
| 92 |
+
|
| 93 |
+
plt.figure(figsize=(8, 6))
|
| 94 |
+
sns.heatmap(cm, annot=True, fmt='d', cmap='Greens')
|
| 95 |
+
plt.title('Confusion Matrix: Elite Stacked Ensemble')
|
| 96 |
+
plt.xlabel('Predicted Label')
|
| 97 |
+
plt.ylabel('True Label')
|
| 98 |
+
plt.savefig(path_utils.get_outputs_path("confusion_matrix_elite.png"))
|
| 99 |
+
plt.close()
|
| 100 |
+
|
| 101 |
+
print("Elite Evaluation Complete.")
|
| 102 |
+
|
| 103 |
+
if __name__ == "__main__":
|
| 104 |
+
run_evaluation()
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pandas
|
| 2 |
+
numpy
|
| 3 |
+
streamlit
|
| 4 |
+
joblib
|
| 5 |
+
plotly
|
| 6 |
+
xgboost
|
| 7 |
+
lightgbm
|
| 8 |
+
catboost
|
| 9 |
+
scikit-learn
|
| 10 |
+
matplotlib
|
| 11 |
+
seaborn
|
| 12 |
+
imbalanced-learn
|
runtime.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
python-3.10.12
|