DIVYANSHI SINGH commited on
Commit
c4fc2e4
·
0 Parent(s):

DiaRisk Elite: Final LFS Production Build

Browse files
.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
+ [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_svg?color=blue)](https://huggingface.co/spaces/YOUR_USERNAME/diarisk-detection-engine)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](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> &nbsp;
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

  • SHA256: f1297c9a9132b5557ce72edee671c8fe54bbd4479acd3c9692286ba4a39b52ad
  • Pointer size: 130 Bytes
  • Size of remote file: 27.1 kB
outputs/confusion_matrix_xgb.png ADDED

Git LFS Details

  • SHA256: 3b3d5f9000fa97e5dc704ea6aea7dabab6c769b86ea54ffc74de358155f15ca5
  • Pointer size: 130 Bytes
  • Size of remote file: 25.6 kB
outputs/correlation_heatmap.png ADDED

Git LFS Details

  • SHA256: 267f6c4e5a226ac067a1e5d58a3f4e3ba70410bb33a6b34b61f266e52a24e83a
  • Pointer size: 130 Bytes
  • Size of remote file: 96.6 kB
outputs/diabetes_distribution.png ADDED

Git LFS Details

  • SHA256: f03bc12a113451c4d69e99b2741b224cc8b40211bc1d0355f2f2322be2095020
  • Pointer size: 130 Bytes
  • Size of remote file: 19 kB
outputs/diabetes_rate_by_age.png ADDED

Git LFS Details

  • SHA256: eea5d482f2f02af181c61a86c1008a1db3aff87bf2ec505f94e8ef0c171b24e2
  • Pointer size: 130 Bytes
  • Size of remote file: 43.8 kB
outputs/diabetes_rate_by_bmi.png ADDED

Git LFS Details

  • SHA256: 78d3c4e7b3c9817a71f79abec5c327999bd11f694e1ae16d84c9547893e7f4fb
  • Pointer size: 130 Bytes
  • Size of remote file: 22.7 kB
outputs/diabetes_rate_by_genhlth.png ADDED

Git LFS Details

  • SHA256: 478ef305a6cd5feb11e89357d2e65e135b39cdf89353d523b5f820a7dfca7c83
  • Pointer size: 130 Bytes
  • Size of remote file: 24.7 kB
outputs/feature_importance.png ADDED

Git LFS Details

  • SHA256: a8684f990b6197a05e0f82a9ed34259a81b45439d9840ced79f8073729717dfa
  • Pointer size: 130 Bytes
  • Size of remote file: 55 kB
outputs/feature_means_comparison.png ADDED

Git LFS Details

  • SHA256: 15fe68adf51f9b3f1d55577c5240b0a44382e768f5637d1198323dbd7212a02b
  • Pointer size: 130 Bytes
  • Size of remote file: 34.2 kB
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

  • SHA256: 27455844821b7749c44d148bbcfe6e175b0cdae0efb3e500d9c9a1e6133ede5f
  • Pointer size: 130 Bytes
  • Size of remote file: 97.8 kB
outputs/roc_curves_elite_comparison.png ADDED

Git LFS Details

  • SHA256: 590a7b8a563138b38e3a3e7ae5a28e6544d7998bf204f40b423c174552529763
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
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