0xarchit commited on
Commit
22032c4
·
0 Parent(s):

Initial commit: Machine Learning Lab Assignment

Browse files
Files changed (34) hide show
  1. .gitattributes +3 -0
  2. .gitignore +4 -0
  3. Dockerfile +7 -0
  4. README.md +41 -0
  5. dataset/Consumer_Shopping_Trends_2026 (6).csv +3 -0
  6. dataset/f1_strategy_dataset_v4.csv +3 -0
  7. dataset/sleep_health_dataset.csv +3 -0
  8. inference/app.py +217 -0
  9. inference/template/favicon.ico +0 -0
  10. inference/template/index.html +89 -0
  11. inference/template/script.js +254 -0
  12. inference/template/styles.css +426 -0
  13. notebooks/Consumer_Shopping_Trends.ipynb +830 -0
  14. notebooks/F1_Strategy.ipynb +627 -0
  15. notebooks/Sleep_Health_And_Daily_Performance.ipynb +775 -0
  16. outputs/Consumer_Shopping_Trends/metrics/final_metircs.csv +3 -0
  17. outputs/Consumer_Shopping_Trends/metrics/prediction.csv +3 -0
  18. outputs/Consumer_Shopping_Trends/model/feature_columns.json +30 -0
  19. outputs/Consumer_Shopping_Trends/model/label_classes.json +5 -0
  20. outputs/Consumer_Shopping_Trends/model/model.joblib +3 -0
  21. outputs/Consumer_Shopping_Trends/model/target_info.json +4 -0
  22. outputs/F1_Strategy/metrics/final_metircs.csv +3 -0
  23. outputs/F1_Strategy/metrics/prediction.csv +3 -0
  24. outputs/F1_Strategy/model/feature_columns.json +78 -0
  25. outputs/F1_Strategy/model/label_classes.json +4 -0
  26. outputs/F1_Strategy/model/model.joblib +3 -0
  27. outputs/F1_Strategy/model/target_info.json +4 -0
  28. outputs/Sleep_Health_And_Daily_Performance/metrics/final_metircs.csv +3 -0
  29. outputs/Sleep_Health_And_Daily_Performance/metrics/prediction.csv +3 -0
  30. outputs/Sleep_Health_And_Daily_Performance/model/feature_columns.json +69 -0
  31. outputs/Sleep_Health_And_Daily_Performance/model/label_classes.json +6 -0
  32. outputs/Sleep_Health_And_Daily_Performance/model/model.joblib +3 -0
  33. outputs/Sleep_Health_And_Daily_Performance/model/target_info.json +4 -0
  34. requirements.txt +11 -0
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.csv filter=lfs diff=lfs merge=lfs -text
2
+ *.joblib filter=lfs diff=lfs merge=lfs -text
3
+ *.pkl filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .venv
2
+ temp.ipynb
3
+ push.ps1
4
+ __pycache__
Dockerfile ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+ WORKDIR /app
3
+ COPY requirements.txt .
4
+ RUN pip install --no-cache-dir -r requirements.txt
5
+ COPY . .
6
+ EXPOSE 7860
7
+ CMD ["uvicorn", "inference.app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Inference Studio
3
+ emoji: ⚡
4
+ colorFrom: red
5
+ colorTo: blue
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Inference Studio
11
+
12
+ A premium, highly asynchronous web interface for machine learning model inference built with FastAPI and Jinja2.
13
+
14
+ ## Features
15
+ - **Triple Model Support**: Switch between Shopping Trends, F1 Strategy, and Sleep Health models.
16
+ - **Hot Loading**: Seamless transition between models without page refreshes.
17
+ - **Architectural Specs**: Deep dive into dataset distributions and model metrics.
18
+ - **Cyber-Industrial UI**: Glassmorphism aesthetic with high-contrast data visualization.
19
+ - **Auto-Inject Testing**: Quickly test models with real samples from the datasets.
20
+
21
+ ## Setup
22
+ 1. Install dependencies:
23
+ ```bash
24
+ pip install -r requirements.txt
25
+ ```
26
+ 2. Run the server:
27
+ ```bash
28
+ uvicorn inference.app:app --reload
29
+ ```
30
+
31
+ ## Deployment
32
+ This project is pre-configured for HuggingFace Spaces.
33
+ 1. Create a new Space with the Docker SDK.
34
+ 2. Upload the repository contents.
35
+ 3. The Space will automatically build and deploy via the included `Dockerfile`.
36
+
37
+ ## Structure
38
+ - `inference/app.py`: FastAPI backend with async inference logic.
39
+ - `inference/template/`: Frontend assets (HTML, CSS, JS).
40
+ - `outputs/`: Trained model artifacts and metrics.
41
+ - `dataset/`: Source datasets for feature engineering.
dataset/Consumer_Shopping_Trends_2026 (6).csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:344eadef86b03901eb79bbf08bcce3a98bf82e0c3beec84232b8c7718ac282f2
3
+ size 991878
dataset/f1_strategy_dataset_v4.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8d40f07bf5aa95ee3a853bd11373acfbebd220d1d9b606591b6b0ba54db3a668
3
+ size 13166466
dataset/sleep_health_dataset.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9b914ff629dd7031fdd28bd86914308cfaa756e442a25454bdcad811083a9763
3
+ size 14868822
inference/app.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import joblib
4
+ import pandas as pd
5
+ import numpy as np
6
+ import shap
7
+ from pathlib import Path
8
+ from typing import Any
9
+ from fastapi import FastAPI, HTTPException, Request
10
+ from fastapi.responses import FileResponse
11
+ from fastapi.staticfiles import StaticFiles
12
+ from fastapi.templating import Jinja2Templates
13
+ from pydantic import BaseModel
14
+
15
+ BASE_DIR = Path(__file__).resolve().parent
16
+ PROJECT_DIR = BASE_DIR.parent
17
+ OUTPUTS_DIR = PROJECT_DIR / "outputs"
18
+ TEMPLATE_DIR = BASE_DIR / "template"
19
+ DATASET_DIR = PROJECT_DIR / "dataset"
20
+
21
+ MODEL_CONFIG = {
22
+ "consumer": {
23
+ "title": "Shopping Trends",
24
+ "dir": OUTPUTS_DIR / "Consumer_Shopping_Trends",
25
+ "data": DATASET_DIR / "Consumer_Shopping_Trends_2026 (6).csv",
26
+ "url": "https://www.kaggle.com/datasets/minahilfatima12328/consumer-shopping-trends-analysis"
27
+ },
28
+ "f1": {
29
+ "title": "F1 Strategy",
30
+ "dir": OUTPUTS_DIR / "F1_Strategy",
31
+ "data": DATASET_DIR / "f1_strategy_dataset_v4.csv",
32
+ "url": "https://www.kaggle.com/datasets/aadigupta1601/f1-strategy-dataset-pit-stop-prediction"
33
+ },
34
+ "sleep": {
35
+ "title": "Sleep Health",
36
+ "dir": OUTPUTS_DIR / "Sleep_Health_And_Daily_Performance",
37
+ "data": DATASET_DIR / "sleep_health_dataset.csv",
38
+ "url": "https://www.kaggle.com/datasets/mohankrishnathalla/sleep-health-and-daily-performance-dataset"
39
+ },
40
+ }
41
+
42
+ class PredictRequest(BaseModel):
43
+ features: dict[str, Any]
44
+
45
+ app = FastAPI(title="Inference Engine")
46
+ templates = Jinja2Templates(directory=str(TEMPLATE_DIR))
47
+ app.mount("/static", StaticFiles(directory=str(TEMPLATE_DIR)), name="static")
48
+
49
+ ARTIFACTS = {}
50
+
51
+ def clean(obj):
52
+ if isinstance(obj, dict): return {k: clean(v) for k, v in obj.items()}
53
+ if isinstance(obj, (list, tuple)): return [clean(x) for x in obj]
54
+ if hasattr(obj, "item"): return obj.item()
55
+ if pd.isna(obj): return None
56
+ return obj
57
+
58
+ def get_json(p: Path):
59
+ if not p.exists(): return {}
60
+ with p.open("r") as f: return json.load(f)
61
+
62
+ def get_csv_dict(p: Path):
63
+ if not p.exists(): return []
64
+ return pd.read_csv(p).to_dict(orient="records")
65
+
66
+ @app.on_event("startup")
67
+ async def startup():
68
+ for key, cfg in MODEL_CONFIG.items():
69
+ try:
70
+ m_dir = cfg["dir"] / "model"
71
+ met_dir = cfg["dir"] / "metrics"
72
+ m_path = m_dir / "model.joblib"
73
+ if not m_path.exists(): continue
74
+
75
+ df = pd.read_csv(cfg["data"])
76
+ model = joblib.load(m_path)
77
+ feat_cols = get_json(m_dir / "feature_columns.json")
78
+ tg_info = get_json(m_dir / "target_info.json")
79
+ tg = tg_info.get("target_col")
80
+
81
+ # Better background data prep for SHAP
82
+ bg_df = df.head(50).copy()
83
+ if tg in bg_df.columns: bg_df = bg_df.drop(columns=[tg])
84
+
85
+ # Ensure numeric columns are numeric and others are dummy encoded
86
+ for c in bg_df.columns:
87
+ if pd.api.types.is_numeric_dtype(df[c].dtype):
88
+ bg_df[c] = pd.to_numeric(bg_df[c], errors="coerce").fillna(df[c].median())
89
+ else:
90
+ bg_df[c] = bg_df[c].astype(str)
91
+
92
+ bg_enc = pd.get_dummies(bg_df, dtype=float).reindex(columns=feat_cols, fill_value=0.0).astype(float)
93
+
94
+ # Use TreeExplainer if possible, otherwise generic Explainer
95
+ try:
96
+ explainer = shap.TreeExplainer(model, bg_enc)
97
+ except:
98
+ explainer = shap.Explainer(model, bg_enc)
99
+
100
+ ARTIFACTS[key] = {
101
+ "title": cfg["title"],
102
+ "url": cfg["url"],
103
+ "model": model,
104
+ "explainer": explainer,
105
+ "features": feat_cols,
106
+ "labels": get_json(m_dir / "label_classes.json"),
107
+ "target": tg_info,
108
+ "metrics": get_csv_dict(met_dir / "final_metircs.csv"),
109
+ "df": df,
110
+ "path": str(cfg["data"])
111
+ }
112
+ except Exception as e:
113
+ print(f"Error loading {key}: {e}")
114
+
115
+ @app.get("/")
116
+ async def index(request: Request):
117
+ models = [{"id": k, "name": v["title"]} for k, v in ARTIFACTS.items()]
118
+ return templates.TemplateResponse(request=request, name="index.html", context={"models": models})
119
+
120
+ @app.get("/api/info/{id}")
121
+ async def info(id: str):
122
+ if id not in ARTIFACTS: raise HTTPException(404)
123
+ art = ARTIFACTS[id]
124
+ df = art["df"]
125
+ tg = art["target"].get("target_col")
126
+ dist = df[tg].value_counts(normalize=True).to_dict() if tg in df.columns else {}
127
+ return clean({
128
+ "title": art["title"],
129
+ "url": art["url"],
130
+ "dataset": {
131
+ "rows": len(df),
132
+ "cols": len(df.columns),
133
+ "target": tg,
134
+ "dist": dist
135
+ },
136
+ "model": {
137
+ "type": art["target"].get("model_name"),
138
+ "features": len(art["features"]),
139
+ "labels": art["labels"]
140
+ },
141
+ "metrics": art["metrics"]
142
+ })
143
+
144
+ @app.get("/api/fields/{id}")
145
+ async def fields(id: str):
146
+ if id not in ARTIFACTS: raise HTTPException(404)
147
+ art = ARTIFACTS[id]
148
+ df = art["df"]
149
+ tg = art["target"].get("target_col")
150
+ out = []
151
+ for c in df.columns:
152
+ if c == tg: continue
153
+ t = "number" if pd.api.types.is_numeric_dtype(df[c].dtype) else "select"
154
+ opts = [x for x in df[c].unique().tolist() if pd.notna(x)] if t == "select" else []
155
+ out.append({"name": c, "type": t, "options": clean(opts), "default": ""})
156
+ return out
157
+
158
+ @app.get("/api/sample/{id}")
159
+ async def sample(id: str):
160
+ if id not in ARTIFACTS: raise HTTPException(404)
161
+ art = ARTIFACTS[id]
162
+ row = art["df"].sample(1).iloc[0].to_dict()
163
+ return clean(row)
164
+
165
+ @app.post("/api/run/{id}")
166
+ async def run(id: str, req: PredictRequest):
167
+ if id not in ARTIFACTS: raise HTTPException(404)
168
+ art = ARTIFACTS[id]
169
+
170
+ try:
171
+ raw = req.features
172
+ cols = art["df"].columns
173
+ tg = art["target"].get("target_col")
174
+ row = {c: raw.get(c, art["df"][c].iloc[0]) for c in cols if c != tg}
175
+ df = pd.DataFrame([row])
176
+
177
+ for c in df.columns:
178
+ if pd.api.types.is_numeric_dtype(art["df"][c].dtype):
179
+ df[c] = pd.to_numeric(df[c], errors="coerce").fillna(art["df"][c].median())
180
+
181
+ enc = pd.get_dummies(df).reindex(columns=art["features"], fill_value=0.0).astype(float)
182
+
183
+ pred = await asyncio.to_thread(art["model"].predict, enc)
184
+ prob = await asyncio.to_thread(art["model"].predict_proba, enc)
185
+
186
+ shap_values = await asyncio.to_thread(art["explainer"].shap_values, enc)
187
+
188
+ idx = int(pred[0])
189
+ lbl = art["labels"][idx] if idx < len(art["labels"]) else str(idx)
190
+ probs = {art["labels"][i] if i < len(art["labels"]) else str(i): float(p) for i, p in enumerate(prob[0])}
191
+
192
+ # SHAP values for tree models (like XGBoost) often return a list for each class
193
+ if isinstance(shap_values, list):
194
+ sv = shap_values[idx][0]
195
+ elif len(shap_values.shape) == 3:
196
+ sv = shap_values[0, :, idx]
197
+ else:
198
+ sv = shap_values[0]
199
+
200
+ contributions = {}
201
+ for i, feat in enumerate(art["features"]):
202
+ orig = feat.split("_")[0]
203
+ contributions[orig] = contributions.get(orig, 0) + float(sv[i])
204
+
205
+ top_contrib = dict(sorted(contributions.items(), key=lambda x: abs(x[1]), reverse=True)[:5])
206
+
207
+ return {
208
+ "result": lbl,
209
+ "scores": probs,
210
+ "shap": top_contrib
211
+ }
212
+ except Exception as e:
213
+ print(f"Prediction Error: {e}")
214
+ raise HTTPException(400, str(e))
215
+
216
+ @app.get("/favicon.ico")
217
+ async def fav(): return FileResponse(str(TEMPLATE_DIR / "favicon.ico"))
inference/template/favicon.ico ADDED
inference/template/index.html ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Inference Studio | Neural Network Visualizer</title>
7
+ <link rel="stylesheet" href="/static/styles.css">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;700&family=JetBrains+Mono:wght@300;500&display=swap" rel="stylesheet">
12
+ </head>
13
+ <body>
14
+ <div class="app-container">
15
+ <header>
16
+ <div class="logo">
17
+ <span class="glitch" data-text="INFERENCE">INFERENCE</span>
18
+ <span class="studio">STUDIO</span>
19
+ </div>
20
+ <nav class="tabs">
21
+ {% for model in models %}
22
+ <button class="tab-btn {% if loop.first %}active{% endif %}" data-id="{{ model.id }}">
23
+ {{ model.name }}
24
+ </button>
25
+ {% endfor %}
26
+ </nav>
27
+ </header>
28
+
29
+ <main>
30
+ <section class="viewport">
31
+ <div class="panel-left">
32
+ <div class="card glass">
33
+ <div class="card-header">
34
+ <h3>Predictive Input</h3>
35
+ <button class="info-trigger" title="Model Specifications">
36
+ <i class="fa-solid fa-circle-info"></i>
37
+ </button>
38
+ </div>
39
+ <form id="inference-form" class="scrollable">
40
+ <div id="fields-container"></div>
41
+ </form>
42
+ <div class="card-footer">
43
+ <div class="btn-group">
44
+ <button type="button" id="btn-autofill" class="btn-secondary">
45
+ <span class="btn-text">Auto-Inject Sample</span>
46
+ <span class="btn-icon"><i class="fa-solid fa-flask-vial"></i></span>
47
+ </button>
48
+ <button type="submit" form="inference-form" class="btn-primary">
49
+ <span class="btn-text">Execute Inference</span>
50
+ <span class="btn-icon"><i class="fa-solid fa-bolt-lightning"></i></span>
51
+ </button>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <div class="panel-right">
58
+ <div class="card glass result-card">
59
+ <div class="card-header">
60
+ <h3>Output Analysis</h3>
61
+ </div>
62
+ <div id="result-display" class="empty-state">
63
+ <div class="neural-loader">
64
+ <div class="node"></div>
65
+ <div class="node"></div>
66
+ <div class="node"></div>
67
+ </div>
68
+ <p>Awaiting input parameters...</p>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </section>
73
+ </main>
74
+
75
+ <div id="modal-overlay" class="hidden">
76
+ <div class="modal glass">
77
+ <div class="modal-header">
78
+ <h2 id="modal-title">Model Specs</h2>
79
+ <button id="modal-close"><i class="fa-solid fa-xmark"></i></button>
80
+ </div>
81
+ <div class="modal-content">
82
+ <div id="specs-content"></div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <script src="/static/script.js"></script>
88
+ </body>
89
+ </html>
inference/template/script.js ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const state = {
2
+ activeId: null,
3
+ fields: {},
4
+ specs: {}
5
+ };
6
+
7
+ const elements = {
8
+ tabs: document.querySelectorAll('.tab-btn'),
9
+ fieldsContainer: document.getElementById('fields-container'),
10
+ inferenceForm: document.getElementById('inference-form'),
11
+ resultDisplay: document.getElementById('result-display'),
12
+ infoTrigger: document.querySelector('.info-trigger'),
13
+ modal: document.getElementById('modal-overlay'),
14
+ modalClose: document.getElementById('modal-close'),
15
+ specsContent: document.getElementById('specs-content'),
16
+ modalTitle: document.getElementById('modal-title'),
17
+ btnAutofill: document.getElementById('btn-autofill')
18
+ };
19
+
20
+ async function autofill() {
21
+ elements.btnAutofill.disabled = true;
22
+ const btnText = elements.btnAutofill.querySelector('.btn-text');
23
+ const originalText = btnText.innerText;
24
+ btnText.innerText = 'Extracting...';
25
+
26
+ try {
27
+ const res = await fetch(`/api/sample/${state.activeId}`);
28
+ const data = await res.json();
29
+
30
+ Object.entries(data).forEach(([key, val]) => {
31
+ const input = elements.inferenceForm.querySelector(`[name="${key}"]`);
32
+ if (input) {
33
+ input.value = val;
34
+ input.style.borderColor = 'var(--secondary)';
35
+ input.style.boxShadow = '0 0 15px rgba(0, 210, 255, 0.3)';
36
+ setTimeout(() => {
37
+ input.style.borderColor = '';
38
+ input.style.boxShadow = '';
39
+ }, 1500);
40
+ }
41
+ });
42
+ } catch (e) {
43
+ console.error(e);
44
+ } finally {
45
+ elements.btnAutofill.disabled = false;
46
+ btnText.innerText = originalText;
47
+ }
48
+ }
49
+
50
+ elements.btnAutofill.onclick = autofill;
51
+
52
+ async function loadTab(id) {
53
+ if (state.activeId === id) return;
54
+
55
+ state.activeId = id;
56
+ elements.tabs.forEach(btn => btn.classList.toggle('active', btn.dataset.id === id));
57
+
58
+ elements.fieldsContainer.style.opacity = '0';
59
+ elements.fieldsContainer.style.transform = 'translateY(10px)';
60
+
61
+ try {
62
+ const [fields, info] = await Promise.all([
63
+ fetch(`/api/fields/${id}`).then(r => r.json()),
64
+ fetch(`/api/info/${id}`).then(r => r.json())
65
+ ]);
66
+
67
+ state.fields[id] = fields;
68
+ state.specs[id] = info;
69
+
70
+ renderFields(fields);
71
+ elements.resultDisplay.innerHTML = `
72
+ <div class="empty-state">
73
+ <div class="neural-loader">
74
+ <div class="node"></div><div class="node"></div><div class="node"></div>
75
+ </div>
76
+ <p>System Initialized. Awaiting Input Data.</p>
77
+ </div>
78
+ `;
79
+ } catch (e) {
80
+ elements.fieldsContainer.innerHTML = `<p class="error">System Error: ${e.message}</p>`;
81
+ } finally {
82
+ setTimeout(() => {
83
+ elements.fieldsContainer.style.opacity = '1';
84
+ elements.fieldsContainer.style.transform = 'translateY(0)';
85
+ elements.fieldsContainer.style.transition = 'all 0.5s cubic-bezier(0.2, 0.8, 0.2, 1)';
86
+ }, 50);
87
+ }
88
+ }
89
+
90
+ function renderFields(fields) {
91
+ elements.fieldsContainer.innerHTML = fields.map((f, i) => `
92
+ <div class="field-group" style="animation: slide-reveal 0.5s ease forwards; animation-delay: ${i * 0.03}s; opacity: 0;">
93
+ <label>${f.name}</label>
94
+ ${f.type === 'select' ? `
95
+ <select name="${f.name}">
96
+ <option value="" disabled selected>Select option...</option>
97
+ ${f.options.map(opt => `<option value="${opt}">${opt}</option>`).join('')}
98
+ </select>
99
+ ` : `
100
+ <input type="number" name="${f.name}" placeholder="Value..." step="any">
101
+ `}
102
+ </div>
103
+ `).join('');
104
+ }
105
+
106
+ elements.inferenceForm.onsubmit = async (e) => {
107
+ e.preventDefault();
108
+ const formData = new FormData(elements.inferenceForm);
109
+ const payload = { features: {} };
110
+ let hasEmpty = false;
111
+
112
+ formData.forEach((v, k) => {
113
+ if (v === "") hasEmpty = true;
114
+ payload.features[k] = v;
115
+ });
116
+
117
+ if (hasEmpty) {
118
+ alert("Please complete all parameters before execution.");
119
+ return;
120
+ }
121
+
122
+ const btn = document.querySelector('.btn-primary');
123
+ const btnText = btn.querySelector('.btn-text');
124
+ const originalText = btnText.innerText;
125
+
126
+ btn.disabled = true;
127
+ btnText.innerText = 'Calculating...';
128
+
129
+ elements.resultDisplay.scrollIntoView({ behavior: 'smooth', block: 'center' });
130
+
131
+ try {
132
+ const res = await fetch(`/api/run/${state.activeId}`, {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify(payload)
136
+ });
137
+ const data = await res.json();
138
+
139
+ if (!res.ok) throw new Error(data.detail || 'Inference engine failure');
140
+ renderResult(data);
141
+ } catch (e) {
142
+ elements.resultDisplay.innerHTML = `
143
+ <div class="empty-state">
144
+ <p style="color: var(--primary)">Execution Error: ${e.message}</p>
145
+ </div>
146
+ `;
147
+ } finally {
148
+ btn.disabled = false;
149
+ btnText.innerText = originalText;
150
+ }
151
+ };
152
+
153
+ function renderResult(data) {
154
+ const scores = Object.entries(data.scores).sort((a, b) => b[1] - a[1]);
155
+ const shap = Object.entries(data.shap || {});
156
+ const maxShap = Math.max(...shap.map(s => Math.abs(s[1])), 1);
157
+
158
+ elements.resultDisplay.innerHTML = `
159
+ <div class="result-view">
160
+ <div class="prediction-box">
161
+ <span style="font-size: 0.65rem; color: var(--text-dim); letter-spacing: 4px; font-weight: 700; text-transform: uppercase;">Prediction Output</span>
162
+ <div class="label-val">${data.result}</div>
163
+ </div>
164
+
165
+ <div class="score-chart">
166
+ <span style="font-size: 0.65rem; color: var(--text-dim); letter-spacing: 4px; font-weight: 700; text-transform: uppercase; margin-bottom: 10px;">Probability Vectors</span>
167
+ ${scores.map(([label, score]) => `
168
+ <div class="score-row">
169
+ <span class="score-label" title="${label}">${label}</span>
170
+ <div class="bar-container">
171
+ <div class="bar-fill" style="width: 0%"></div>
172
+ </div>
173
+ <span class="score-label" style="text-align: right; width: 50px; font-weight: 700; color: var(--text)">${(score * 100).toFixed(1)}%</span>
174
+ </div>
175
+ `).join('')}
176
+ </div>
177
+
178
+ <div class="shap-chart">
179
+ <span style="font-size: 0.65rem; color: var(--text-dim); letter-spacing: 4px; font-weight: 700; text-transform: uppercase; margin-bottom: 10px;">Top Decision Drivers</span>
180
+ ${shap.map(([feat, val]) => `
181
+ <div class="shap-row">
182
+ <span class="score-label" style="width: 120px;" title="${feat}">${feat}</span>
183
+ <div class="shap-bar-container">
184
+ <div class="shap-bar-fill ${val >= 0 ? 'positive' : 'negative'}" style="width: 0%"></div>
185
+ </div>
186
+ <span class="score-label" style="text-align: right; width: 40px;">${val >= 0 ? '+' : '-'}${Math.abs(val).toFixed(2)}</span>
187
+ </div>
188
+ `).join('')}
189
+ </div>
190
+ </div>
191
+ `;
192
+
193
+ setTimeout(() => {
194
+ const fills = elements.resultDisplay.querySelectorAll('.bar-fill');
195
+ scores.forEach((s, i) => fills[i].style.width = `${s[1] * 100}%`);
196
+
197
+ const shapFills = elements.resultDisplay.querySelectorAll('.shap-bar-fill');
198
+ shap.forEach((s, i) => shapFills[i].style.width = `${(Math.abs(s[1]) / maxShap) * 100}%`);
199
+ }, 100);
200
+ }
201
+
202
+ elements.infoTrigger.onclick = () => {
203
+ const info = state.specs[state.activeId];
204
+ if (!info) return;
205
+
206
+ elements.modalTitle.innerText = `${info.title} Architecture`;
207
+ elements.specsContent.innerHTML = `
208
+ <div class="spec-grid">
209
+ <div class="spec-item"><h4>Volume</h4><p>${info.dataset.rows.toLocaleString()}</p></div>
210
+ <div class="spec-item"><h4>Dimensions</h4><p>${info.dataset.cols}</p></div>
211
+ <div class="spec-item"><h4>Model Type</h4><p>${info.model.type || 'Ensemble'}</p></div>
212
+ <div class="spec-item"><h4>Target</h4><p>${info.dataset.target}</p></div>
213
+ </div>
214
+ <div class="metrics-table-wrapper" style="margin-bottom: 30px;">
215
+ <table style="width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 0.8rem;">
216
+ <thead style="text-align: left; color: var(--text-dim); border-bottom: 1px solid var(--border);">
217
+ <tr>${info.metrics.length ? Object.keys(info.metrics[0]).map(k => `<th style="padding: 10px;">${k}</th>`).join('') : ''}</tr>
218
+ </thead>
219
+ <tbody>
220
+ ${info.metrics.map(m => `
221
+ <tr style="border-bottom: 1px solid var(--border);">
222
+ ${Object.values(m).map(v => `<td style="padding: 10px; color: var(--text);">${typeof v === 'number' ? v.toFixed(4) : v}</td>`).join('')}
223
+ </tr>
224
+ `).join('')}
225
+ </tbody>
226
+ </table>
227
+ </div>
228
+ <a href="${info.url}" target="_blank" class="source-link">
229
+ <i class="fa-solid fa-arrow-up-right-from-square"></i> Explore Kaggle Dataset
230
+ </a>
231
+ `;
232
+ elements.modal.classList.remove('hidden');
233
+ document.body.style.overflow = 'hidden';
234
+ };
235
+
236
+ elements.modalClose.onclick = () => {
237
+ elements.modal.classList.add('hidden');
238
+ document.body.style.overflow = 'auto';
239
+ };
240
+
241
+ window.onclick = (e) => {
242
+ if (e.target === elements.modal) {
243
+ elements.modal.classList.add('hidden');
244
+ document.body.style.overflow = 'auto';
245
+ }
246
+ };
247
+
248
+ elements.tabs.forEach(btn => btn.onclick = () => {
249
+ loadTab(btn.dataset.id);
250
+ btn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
251
+ });
252
+
253
+ const initialId = elements.tabs[0]?.dataset.id;
254
+ if (initialId) loadTab(initialId);
inference/template/styles.css ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg: #020204;
3
+ --card: rgba(10, 11, 16, 0.85);
4
+ --primary: #ff3e00;
5
+ --secondary: #00d2ff;
6
+ --text: #ffffff;
7
+ --text-dim: #94a3b8;
8
+ --accent: #facc15;
9
+ --border: rgba(255, 255, 255, 0.08);
10
+ --glow: rgba(255, 62, 0, 0.15);
11
+ --font-main: 'Space Grotesk', sans-serif;
12
+ --font-mono: 'JetBrains Mono', monospace;
13
+ }
14
+
15
+ * {
16
+ margin: 0;
17
+ padding: 0;
18
+ box-sizing: border-box;
19
+ -webkit-tap-highlight-color: transparent;
20
+ }
21
+
22
+ body {
23
+ background: var(--bg);
24
+ color: var(--text);
25
+ font-family: var(--font-main);
26
+ min-height: 100vh;
27
+ background-image:
28
+ radial-gradient(at 0% 0%, rgba(255, 62, 0, 0.1) 0px, transparent 50%),
29
+ radial-gradient(at 100% 100%, rgba(0, 210, 255, 0.1) 0px, transparent 50%),
30
+ radial-gradient(at 50% 50%, rgba(15, 23, 42, 1) 0px, transparent 80%);
31
+ background-attachment: fixed;
32
+ }
33
+
34
+ .app-container {
35
+ max-width: 1400px;
36
+ margin: 0 auto;
37
+ padding: 20px;
38
+ display: flex;
39
+ flex-direction: column;
40
+ min-height: 100vh;
41
+ position: relative;
42
+ z-index: 1;
43
+ }
44
+
45
+ header {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: 20px;
49
+ padding: 30px 0;
50
+ border-bottom: 1px solid var(--border);
51
+ margin-bottom: 30px;
52
+ }
53
+
54
+ .logo {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ }
59
+
60
+ .glitch {
61
+ font-weight: 800;
62
+ font-size: 2.5rem;
63
+ letter-spacing: -1px;
64
+ color: var(--primary);
65
+ background: linear-gradient(to right, var(--primary), var(--secondary));
66
+ -webkit-background-clip: text;
67
+ -webkit-text-fill-color: transparent;
68
+ filter: drop-shadow(0 0 10px var(--glow));
69
+ }
70
+
71
+ .studio {
72
+ font-size: 0.75rem;
73
+ color: var(--text-dim);
74
+ letter-spacing: 10px;
75
+ text-transform: uppercase;
76
+ font-weight: 300;
77
+ margin-top: -5px;
78
+ }
79
+
80
+ .tabs {
81
+ display: flex;
82
+ gap: 12px;
83
+ justify-content: center;
84
+ overflow-x: auto;
85
+ padding: 10px;
86
+ scrollbar-width: none;
87
+ }
88
+
89
+ .tabs::-webkit-scrollbar { display: none; }
90
+
91
+ .tab-btn {
92
+ background: rgba(255, 255, 255, 0.03);
93
+ border: 1px solid var(--border);
94
+ color: var(--text-dim);
95
+ padding: 14px 28px;
96
+ border-radius: 100px;
97
+ cursor: pointer;
98
+ font-weight: 600;
99
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
100
+ white-space: nowrap;
101
+ }
102
+
103
+ .tab-btn.active {
104
+ background: var(--text);
105
+ color: var(--bg);
106
+ border-color: var(--text);
107
+ box-shadow: 0 0 30px rgba(255, 255, 255, 0.2);
108
+ transform: scale(1.05);
109
+ }
110
+
111
+ .viewport {
112
+ display: grid;
113
+ grid-template-columns: 1fr;
114
+ gap: 30px;
115
+ flex: 1;
116
+ }
117
+
118
+ .card {
119
+ background: var(--card);
120
+ border-radius: 32px;
121
+ border: 1px solid var(--border);
122
+ display: flex;
123
+ flex-direction: column;
124
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
125
+ backdrop-filter: blur(40px);
126
+ transition: transform 0.3s ease, border-color 0.3s ease;
127
+ }
128
+
129
+ .card-header {
130
+ padding: 25px 30px;
131
+ border-bottom: 1px solid var(--border);
132
+ display: flex;
133
+ justify-content: space-between;
134
+ align-items: center;
135
+ }
136
+
137
+ .card-header h3 {
138
+ font-size: 1rem;
139
+ font-weight: 700;
140
+ color: var(--text);
141
+ letter-spacing: 1px;
142
+ }
143
+
144
+ .info-trigger {
145
+ width: 40px;
146
+ height: 40px;
147
+ border-radius: 50%;
148
+ border: 1px solid var(--border);
149
+ background: rgba(255, 255, 255, 0.05);
150
+ color: var(--text-dim);
151
+ cursor: pointer;
152
+ transition: all 0.3s;
153
+ }
154
+
155
+ .info-trigger:hover {
156
+ background: var(--secondary);
157
+ color: var(--bg);
158
+ border-color: var(--secondary);
159
+ transform: rotate(15deg);
160
+ }
161
+
162
+ #fields-container {
163
+ padding: 30px;
164
+ display: grid;
165
+ grid-template-columns: 1fr;
166
+ gap: 20px;
167
+ }
168
+
169
+ .field-group {
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 10px;
173
+ }
174
+
175
+ label {
176
+ font-size: 0.75rem;
177
+ color: var(--text-dim);
178
+ text-transform: uppercase;
179
+ font-family: var(--font-mono);
180
+ font-weight: 500;
181
+ }
182
+
183
+ input, select {
184
+ background: rgba(255, 255, 255, 0.02);
185
+ border: 1px solid var(--border);
186
+ color: var(--text);
187
+ padding: 16px;
188
+ border-radius: 16px;
189
+ font-family: var(--font-mono);
190
+ font-size: 1rem;
191
+ outline: none;
192
+ transition: all 0.3s;
193
+ }
194
+
195
+ input:focus, select:focus {
196
+ background: rgba(255, 255, 255, 0.05);
197
+ border-color: var(--secondary);
198
+ box-shadow: 0 0 0 4px rgba(0, 210, 255, 0.1);
199
+ }
200
+
201
+ option { background: #0f172a; color: #fff; }
202
+
203
+ .card-footer {
204
+ padding: 30px;
205
+ border-top: 1px solid var(--border);
206
+ }
207
+
208
+ .btn-group {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 12px;
212
+ }
213
+
214
+ .btn-primary, .btn-secondary {
215
+ padding: 20px;
216
+ border-radius: 18px;
217
+ font-weight: 700;
218
+ font-size: 1rem;
219
+ cursor: pointer;
220
+ display: flex;
221
+ align-items: center;
222
+ justify-content: center;
223
+ gap: 12px;
224
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
225
+ }
226
+
227
+ .btn-primary {
228
+ background: var(--primary);
229
+ color: #fff;
230
+ border: none;
231
+ box-shadow: 0 10px 20px rgba(255, 62, 0, 0.2);
232
+ }
233
+
234
+ .btn-primary:hover {
235
+ transform: translateY(-4px);
236
+ box-shadow: 0 15px 30px rgba(255, 62, 0, 0.4);
237
+ }
238
+
239
+ .btn-secondary {
240
+ background: transparent;
241
+ border: 1px solid var(--border);
242
+ color: var(--text);
243
+ }
244
+
245
+ .btn-secondary:hover {
246
+ background: rgba(255, 255, 255, 0.05);
247
+ border-color: var(--text-dim);
248
+ }
249
+
250
+ .empty-state {
251
+ padding: 80px 40px;
252
+ text-align: center;
253
+ color: var(--text-dim);
254
+ }
255
+
256
+ .neural-loader { display: flex; gap: 15px; margin-bottom: 20px; }
257
+ .node {
258
+ width: 14px;
259
+ height: 14px;
260
+ border-radius: 50%;
261
+ background: var(--primary);
262
+ animation: glow-pulse 1.5s infinite ease-in-out;
263
+ }
264
+ .node:nth-child(2) { animation-delay: 0.2s; background: var(--secondary); }
265
+ .node:nth-child(3) { animation-delay: 0.4s; background: var(--accent); }
266
+
267
+ @keyframes glow-pulse {
268
+ 0%, 100% { transform: scale(1); opacity: 0.3; filter: blur(0px); }
269
+ 50% { transform: scale(1.4); opacity: 1; filter: blur(4px); }
270
+ }
271
+
272
+ .result-view {
273
+ padding: 40px;
274
+ animation: slide-reveal 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
275
+ }
276
+
277
+ .prediction-box {
278
+ text-align: center;
279
+ padding: 40px;
280
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent);
281
+ border-radius: 24px;
282
+ margin-bottom: 40px;
283
+ border: 1px solid var(--border);
284
+ }
285
+
286
+ .label-val {
287
+ font-size: 3.5rem;
288
+ font-weight: 800;
289
+ color: var(--primary);
290
+ text-shadow: 0 0 30px var(--glow);
291
+ margin-top: 10px;
292
+ }
293
+
294
+ .score-chart { display: flex; flex-direction: column; gap: 20px; }
295
+ .score-row { display: flex; align-items: center; gap: 15px; }
296
+ .score-label { width: 100px; font-size: 0.8rem; color: var(--text-dim); font-family: var(--font-mono); }
297
+ .bar-container { flex: 1; height: 12px; background: rgba(255, 255, 255, 0.03); border-radius: 100px; overflow: hidden; }
298
+ .bar-fill { height: 100%; background: linear-gradient(90deg, var(--secondary), var(--primary)); transition: width 1.5s ease-out; }
299
+
300
+ .shap-chart {
301
+ display: flex;
302
+ flex-direction: column;
303
+ gap: 15px;
304
+ margin-top: 40px;
305
+ padding-top: 40px;
306
+ border-top: 1px solid var(--border);
307
+ }
308
+
309
+ .shap-row {
310
+ display: flex;
311
+ align-items: center;
312
+ gap: 15px;
313
+ }
314
+
315
+ .shap-bar-container {
316
+ flex: 1;
317
+ height: 8px;
318
+ background: rgba(255, 255, 255, 0.03);
319
+ border-radius: 100px;
320
+ position: relative;
321
+ overflow: hidden;
322
+ }
323
+
324
+ .shap-bar-fill {
325
+ height: 100%;
326
+ border-radius: 100px;
327
+ transition: width 1s ease-out;
328
+ }
329
+
330
+ .shap-bar-fill.positive {
331
+ background: #10b981;
332
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
333
+ }
334
+
335
+ .shap-bar-fill.negative {
336
+ background: #ef4444;
337
+ box-shadow: 0 0 10px rgba(239, 68, 68, 0.3);
338
+ }
339
+
340
+ #modal-overlay {
341
+ position: fixed;
342
+ top: 0;
343
+ left: 0;
344
+ width: 100%;
345
+ height: 100%;
346
+ background: rgba(0, 0, 0, 0.95);
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ z-index: 1000;
351
+ backdrop-filter: blur(10px);
352
+ }
353
+
354
+ .hidden { display: none !important; }
355
+
356
+ .modal {
357
+ width: 90%;
358
+ max-width: 700px;
359
+ background: #08080a;
360
+ border: 1px solid var(--border);
361
+ border-radius: 40px;
362
+ overflow: hidden;
363
+ animation: modal-enter 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
364
+ }
365
+
366
+ .modal-header {
367
+ padding: 25px 40px;
368
+ border-bottom: 1px solid var(--border);
369
+ display: flex;
370
+ justify-content: space-between;
371
+ align-items: center;
372
+ }
373
+
374
+ #modal-close {
375
+ background: rgba(255, 255, 255, 0.05);
376
+ border: 1px solid var(--border);
377
+ color: var(--text-dim);
378
+ width: 36px;
379
+ height: 36px;
380
+ border-radius: 10px;
381
+ cursor: pointer;
382
+ transition: all 0.3s;
383
+ display: flex;
384
+ align-items: center;
385
+ justify-content: center;
386
+ }
387
+
388
+ #modal-close:hover {
389
+ background: var(--primary);
390
+ color: #fff;
391
+ border-color: var(--primary);
392
+ transform: rotate(90deg);
393
+ }
394
+
395
+ .modal-content { padding: 40px; overflow-y: auto; max-height: 80vh; }
396
+ .spec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; }
397
+ .spec-item { background: rgba(255, 255, 255, 0.02); padding: 20px; border-radius: 24px; border: 1px solid var(--border); }
398
+ .spec-item h4 { font-size: 0.7rem; color: var(--secondary); text-transform: uppercase; margin-bottom: 8px; }
399
+ .spec-item p { font-size: 1.2rem; font-weight: 700; font-family: var(--font-mono); }
400
+
401
+ .source-link {
402
+ display: inline-flex;
403
+ align-items: center;
404
+ gap: 10px;
405
+ margin-top: 20px;
406
+ color: var(--secondary);
407
+ text-decoration: none;
408
+ font-weight: 600;
409
+ font-size: 0.9rem;
410
+ padding: 12px 24px;
411
+ background: rgba(0, 210, 255, 0.1);
412
+ border-radius: 100px;
413
+ transition: all 0.3s;
414
+ }
415
+
416
+ .source-link:hover { background: var(--secondary); color: var(--bg); }
417
+
418
+ @media (min-width: 1024px) {
419
+ .viewport { grid-template-columns: 1fr 1fr; }
420
+ header { flex-direction: row; justify-content: space-between; align-items: center; }
421
+ .logo { align-items: flex-start; }
422
+ .btn-group { flex-direction: row; }
423
+ }
424
+
425
+ @keyframes slide-reveal { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
426
+ @keyframes modal-enter { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
notebooks/Consumer_Shopping_Trends.ipynb ADDED
@@ -0,0 +1,830 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "6811879a",
6
+ "metadata": {},
7
+ "source": [
8
+ "## Step 1: Import Libraries"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": 30,
14
+ "id": "7b3d945e",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import os\n",
19
+ "import json\n",
20
+ "import joblib\n",
21
+ "import pandas as pd\n",
22
+ "import matplotlib.pyplot as plt\n",
23
+ "import seaborn as sns\n",
24
+ "\n",
25
+ "from sklearn.model_selection import train_test_split\n",
26
+ "from sklearn.preprocessing import LabelEncoder\n",
27
+ "from sklearn.linear_model import LogisticRegression\n",
28
+ "from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, f1_score, log_loss\n",
29
+ "\n",
30
+ "sns.set_style('whitegrid')\n",
31
+ "pd.set_option('display.max_columns', None)"
32
+ ]
33
+ },
34
+ {
35
+ "cell_type": "markdown",
36
+ "id": "5bdfd552",
37
+ "metadata": {},
38
+ "source": [
39
+ "## Step 2: Load Dataset"
40
+ ]
41
+ },
42
+ {
43
+ "cell_type": "code",
44
+ "execution_count": 31,
45
+ "id": "c92ab153",
46
+ "metadata": {},
47
+ "outputs": [
48
+ {
49
+ "name": "stdout",
50
+ "output_type": "stream",
51
+ "text": [
52
+ "Dataset shape: (11789, 25)\n"
53
+ ]
54
+ },
55
+ {
56
+ "data": {
57
+ "text/html": [
58
+ "<div>\n",
59
+ "<style scoped>\n",
60
+ " .dataframe tbody tr th:only-of-type {\n",
61
+ " vertical-align: middle;\n",
62
+ " }\n",
63
+ "\n",
64
+ " .dataframe tbody tr th {\n",
65
+ " vertical-align: top;\n",
66
+ " }\n",
67
+ "\n",
68
+ " .dataframe thead th {\n",
69
+ " text-align: right;\n",
70
+ " }\n",
71
+ "</style>\n",
72
+ "<table border=\"1\" class=\"dataframe\">\n",
73
+ " <thead>\n",
74
+ " <tr style=\"text-align: right;\">\n",
75
+ " <th></th>\n",
76
+ " <th>age</th>\n",
77
+ " <th>monthly_income</th>\n",
78
+ " <th>daily_internet_hours</th>\n",
79
+ " <th>smartphone_usage_years</th>\n",
80
+ " <th>social_media_hours</th>\n",
81
+ " <th>online_payment_trust_score</th>\n",
82
+ " <th>tech_savvy_score</th>\n",
83
+ " <th>monthly_online_orders</th>\n",
84
+ " <th>monthly_store_visits</th>\n",
85
+ " <th>avg_online_spend</th>\n",
86
+ " <th>avg_store_spend</th>\n",
87
+ " <th>discount_sensitivity</th>\n",
88
+ " <th>return_frequency</th>\n",
89
+ " <th>avg_delivery_days</th>\n",
90
+ " <th>delivery_fee_sensitivity</th>\n",
91
+ " <th>free_return_importance</th>\n",
92
+ " <th>product_availability_online</th>\n",
93
+ " <th>impulse_buying_score</th>\n",
94
+ " <th>need_touch_feel_score</th>\n",
95
+ " <th>brand_loyalty_score</th>\n",
96
+ " <th>environmental_awareness</th>\n",
97
+ " <th>time_pressure_level</th>\n",
98
+ " <th>gender</th>\n",
99
+ " <th>city_tier</th>\n",
100
+ " <th>shopping_preference</th>\n",
101
+ " </tr>\n",
102
+ " </thead>\n",
103
+ " <tbody>\n",
104
+ " <tr>\n",
105
+ " <th>0</th>\n",
106
+ " <td>56</td>\n",
107
+ " <td>221111</td>\n",
108
+ " <td>6.5</td>\n",
109
+ " <td>12</td>\n",
110
+ " <td>0.7</td>\n",
111
+ " <td>1</td>\n",
112
+ " <td>6</td>\n",
113
+ " <td>16</td>\n",
114
+ " <td>16</td>\n",
115
+ " <td>28551</td>\n",
116
+ " <td>144092</td>\n",
117
+ " <td>2</td>\n",
118
+ " <td>3</td>\n",
119
+ " <td>2</td>\n",
120
+ " <td>6</td>\n",
121
+ " <td>7</td>\n",
122
+ " <td>7</td>\n",
123
+ " <td>1</td>\n",
124
+ " <td>3</td>\n",
125
+ " <td>6</td>\n",
126
+ " <td>5</td>\n",
127
+ " <td>2</td>\n",
128
+ " <td>Other</td>\n",
129
+ " <td>Tier 3</td>\n",
130
+ " <td>Store</td>\n",
131
+ " </tr>\n",
132
+ " <tr>\n",
133
+ " <th>1</th>\n",
134
+ " <td>69</td>\n",
135
+ " <td>96029</td>\n",
136
+ " <td>8.2</td>\n",
137
+ " <td>13</td>\n",
138
+ " <td>2.7</td>\n",
139
+ " <td>6</td>\n",
140
+ " <td>9</td>\n",
141
+ " <td>14</td>\n",
142
+ " <td>1</td>\n",
143
+ " <td>124056</td>\n",
144
+ " <td>28421</td>\n",
145
+ " <td>4</td>\n",
146
+ " <td>7</td>\n",
147
+ " <td>4</td>\n",
148
+ " <td>1</td>\n",
149
+ " <td>3</td>\n",
150
+ " <td>4</td>\n",
151
+ " <td>9</td>\n",
152
+ " <td>6</td>\n",
153
+ " <td>8</td>\n",
154
+ " <td>1</td>\n",
155
+ " <td>7</td>\n",
156
+ " <td>Male</td>\n",
157
+ " <td>Tier 3</td>\n",
158
+ " <td>Hybrid</td>\n",
159
+ " </tr>\n",
160
+ " <tr>\n",
161
+ " <th>2</th>\n",
162
+ " <td>46</td>\n",
163
+ " <td>19055</td>\n",
164
+ " <td>6.4</td>\n",
165
+ " <td>4</td>\n",
166
+ " <td>2.1</td>\n",
167
+ " <td>10</td>\n",
168
+ " <td>8</td>\n",
169
+ " <td>2</td>\n",
170
+ " <td>0</td>\n",
171
+ " <td>81939</td>\n",
172
+ " <td>128229</td>\n",
173
+ " <td>9</td>\n",
174
+ " <td>4</td>\n",
175
+ " <td>5</td>\n",
176
+ " <td>3</td>\n",
177
+ " <td>4</td>\n",
178
+ " <td>10</td>\n",
179
+ " <td>1</td>\n",
180
+ " <td>1</td>\n",
181
+ " <td>3</td>\n",
182
+ " <td>3</td>\n",
183
+ " <td>3</td>\n",
184
+ " <td>Female</td>\n",
185
+ " <td>Tier 3</td>\n",
186
+ " <td>Store</td>\n",
187
+ " </tr>\n",
188
+ " <tr>\n",
189
+ " <th>3</th>\n",
190
+ " <td>32</td>\n",
191
+ " <td>53170</td>\n",
192
+ " <td>6.4</td>\n",
193
+ " <td>11</td>\n",
194
+ " <td>0.7</td>\n",
195
+ " <td>2</td>\n",
196
+ " <td>10</td>\n",
197
+ " <td>20</td>\n",
198
+ " <td>3</td>\n",
199
+ " <td>35901</td>\n",
200
+ " <td>134650</td>\n",
201
+ " <td>7</td>\n",
202
+ " <td>0</td>\n",
203
+ " <td>3</td>\n",
204
+ " <td>3</td>\n",
205
+ " <td>10</td>\n",
206
+ " <td>2</td>\n",
207
+ " <td>4</td>\n",
208
+ " <td>8</td>\n",
209
+ " <td>2</td>\n",
210
+ " <td>6</td>\n",
211
+ " <td>6</td>\n",
212
+ " <td>Female</td>\n",
213
+ " <td>Tier 1</td>\n",
214
+ " <td>Store</td>\n",
215
+ " </tr>\n",
216
+ " <tr>\n",
217
+ " <th>4</th>\n",
218
+ " <td>60</td>\n",
219
+ " <td>244016</td>\n",
220
+ " <td>6.0</td>\n",
221
+ " <td>5</td>\n",
222
+ " <td>0.7</td>\n",
223
+ " <td>2</td>\n",
224
+ " <td>5</td>\n",
225
+ " <td>18</td>\n",
226
+ " <td>16</td>\n",
227
+ " <td>131971</td>\n",
228
+ " <td>34122</td>\n",
229
+ " <td>5</td>\n",
230
+ " <td>9</td>\n",
231
+ " <td>2</td>\n",
232
+ " <td>4</td>\n",
233
+ " <td>2</td>\n",
234
+ " <td>5</td>\n",
235
+ " <td>8</td>\n",
236
+ " <td>9</td>\n",
237
+ " <td>7</td>\n",
238
+ " <td>1</td>\n",
239
+ " <td>6</td>\n",
240
+ " <td>Male</td>\n",
241
+ " <td>Tier 3</td>\n",
242
+ " <td>Store</td>\n",
243
+ " </tr>\n",
244
+ " </tbody>\n",
245
+ "</table>\n",
246
+ "</div>"
247
+ ],
248
+ "text/plain": [
249
+ " age monthly_income daily_internet_hours smartphone_usage_years \\\n",
250
+ "0 56 221111 6.5 12 \n",
251
+ "1 69 96029 8.2 13 \n",
252
+ "2 46 19055 6.4 4 \n",
253
+ "3 32 53170 6.4 11 \n",
254
+ "4 60 244016 6.0 5 \n",
255
+ "\n",
256
+ " social_media_hours online_payment_trust_score tech_savvy_score \\\n",
257
+ "0 0.7 1 6 \n",
258
+ "1 2.7 6 9 \n",
259
+ "2 2.1 10 8 \n",
260
+ "3 0.7 2 10 \n",
261
+ "4 0.7 2 5 \n",
262
+ "\n",
263
+ " monthly_online_orders monthly_store_visits avg_online_spend \\\n",
264
+ "0 16 16 28551 \n",
265
+ "1 14 1 124056 \n",
266
+ "2 2 0 81939 \n",
267
+ "3 20 3 35901 \n",
268
+ "4 18 16 131971 \n",
269
+ "\n",
270
+ " avg_store_spend discount_sensitivity return_frequency avg_delivery_days \\\n",
271
+ "0 144092 2 3 2 \n",
272
+ "1 28421 4 7 4 \n",
273
+ "2 128229 9 4 5 \n",
274
+ "3 134650 7 0 3 \n",
275
+ "4 34122 5 9 2 \n",
276
+ "\n",
277
+ " delivery_fee_sensitivity free_return_importance \\\n",
278
+ "0 6 7 \n",
279
+ "1 1 3 \n",
280
+ "2 3 4 \n",
281
+ "3 3 10 \n",
282
+ "4 4 2 \n",
283
+ "\n",
284
+ " product_availability_online impulse_buying_score need_touch_feel_score \\\n",
285
+ "0 7 1 3 \n",
286
+ "1 4 9 6 \n",
287
+ "2 10 1 1 \n",
288
+ "3 2 4 8 \n",
289
+ "4 5 8 9 \n",
290
+ "\n",
291
+ " brand_loyalty_score environmental_awareness time_pressure_level gender \\\n",
292
+ "0 6 5 2 Other \n",
293
+ "1 8 1 7 Male \n",
294
+ "2 3 3 3 Female \n",
295
+ "3 2 6 6 Female \n",
296
+ "4 7 1 6 Male \n",
297
+ "\n",
298
+ " city_tier shopping_preference \n",
299
+ "0 Tier 3 Store \n",
300
+ "1 Tier 3 Hybrid \n",
301
+ "2 Tier 3 Store \n",
302
+ "3 Tier 1 Store \n",
303
+ "4 Tier 3 Store "
304
+ ]
305
+ },
306
+ "metadata": {},
307
+ "output_type": "display_data"
308
+ },
309
+ {
310
+ "name": "stdout",
311
+ "output_type": "stream",
312
+ "text": [
313
+ "shopping_preference\n",
314
+ "Store 10244\n",
315
+ "Online 1176\n",
316
+ "Hybrid 369\n",
317
+ "Name: count, dtype: int64\n"
318
+ ]
319
+ }
320
+ ],
321
+ "source": [
322
+ "data_path = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\dataset\\Consumer_Shopping_Trends_2026 (6).csv'\n",
323
+ "\n",
324
+ "df = pd.read_csv(data_path)\n",
325
+ "\n",
326
+ "print('Dataset shape:', df.shape)\n",
327
+ "display(df.head())\n",
328
+ "\n",
329
+ "target_column = 'shopping_preference'\n",
330
+ "print(df[target_column].value_counts())"
331
+ ]
332
+ },
333
+ {
334
+ "cell_type": "markdown",
335
+ "id": "5007a197",
336
+ "metadata": {},
337
+ "source": [
338
+ "## Step 3: Prepare Data"
339
+ ]
340
+ },
341
+ {
342
+ "cell_type": "code",
343
+ "execution_count": 32,
344
+ "id": "ce15f887",
345
+ "metadata": {},
346
+ "outputs": [
347
+ {
348
+ "name": "stdout",
349
+ "output_type": "stream",
350
+ "text": [
351
+ "X_train shape: (9431, 28)\n",
352
+ "X_test shape: (2358, 28)\n",
353
+ "Classes: ['Hybrid', 'Online', 'Store']\n"
354
+ ]
355
+ }
356
+ ],
357
+ "source": [
358
+ "X = df.drop(columns=[target_column])\n",
359
+ "y_text = df[target_column]\n",
360
+ "\n",
361
+ "label_encoder = LabelEncoder()\n",
362
+ "y = label_encoder.fit_transform(y_text)\n",
363
+ "\n",
364
+ "X = pd.get_dummies(X, drop_first=False)\n",
365
+ "\n",
366
+ "X_train, X_test, y_train, y_test = train_test_split(\n",
367
+ " X,\n",
368
+ " y,\n",
369
+ " test_size=0.2,\n",
370
+ " random_state=3,\n",
371
+ " stratify=y\n",
372
+ ")\n",
373
+ "\n",
374
+ "print('X_train shape:', X_train.shape)\n",
375
+ "print('X_test shape:', X_test.shape)\n",
376
+ "print('Classes:', list(label_encoder.classes_))"
377
+ ]
378
+ },
379
+ {
380
+ "cell_type": "markdown",
381
+ "id": "9254a2e1",
382
+ "metadata": {},
383
+ "source": [
384
+ "## Step 4: Train Logistic Regression Model"
385
+ ]
386
+ },
387
+ {
388
+ "cell_type": "code",
389
+ "execution_count": 33,
390
+ "id": "f01a450c",
391
+ "metadata": {},
392
+ "outputs": [
393
+ {
394
+ "name": "stdout",
395
+ "output_type": "stream",
396
+ "text": [
397
+ "Model training completed\n"
398
+ ]
399
+ },
400
+ {
401
+ "name": "stderr",
402
+ "output_type": "stream",
403
+ "text": [
404
+ "d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\.venv\\Lib\\site-packages\\sklearn\\linear_model\\_logistic.py:406: ConvergenceWarning: lbfgs failed to converge after 5000 iteration(s) (status=1):\n",
405
+ "STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT\n",
406
+ "\n",
407
+ "Increase the number of iterations to improve the convergence (max_iter=5000).\n",
408
+ "You might also want to scale the data as shown in:\n",
409
+ " https://scikit-learn.org/stable/modules/preprocessing.html\n",
410
+ "Please also refer to the documentation for alternative solver options:\n",
411
+ " https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n",
412
+ " n_iter_i = _check_optimize_result(\n"
413
+ ]
414
+ }
415
+ ],
416
+ "source": [
417
+ "model = LogisticRegression(max_iter=5000)\n",
418
+ "model.fit(X_train, y_train)\n",
419
+ "\n",
420
+ "y_pred = model.predict(X_test)\n",
421
+ "y_prob = model.predict_proba(X_test)\n",
422
+ "\n",
423
+ "print('Model training completed')"
424
+ ]
425
+ },
426
+ {
427
+ "cell_type": "markdown",
428
+ "id": "02bf8a01",
429
+ "metadata": {},
430
+ "source": [
431
+ "## Step 5: Check Model Performance"
432
+ ]
433
+ },
434
+ {
435
+ "cell_type": "code",
436
+ "execution_count": 34,
437
+ "id": "d3fec84a",
438
+ "metadata": {},
439
+ "outputs": [
440
+ {
441
+ "name": "stdout",
442
+ "output_type": "stream",
443
+ "text": [
444
+ "Accuracy: 0.9881\n",
445
+ "Weighted F1 Score: 0.9883\n",
446
+ "Log Loss: 0.0237\n",
447
+ "\n",
448
+ "Classification Report:\n",
449
+ "\n",
450
+ " precision recall f1-score support\n",
451
+ "\n",
452
+ " Hybrid 0.79 0.85 0.82 74\n",
453
+ " Online 0.97 0.96 0.97 235\n",
454
+ " Store 1.00 1.00 1.00 2049\n",
455
+ "\n",
456
+ " accuracy 0.99 2358\n",
457
+ " macro avg 0.92 0.94 0.93 2358\n",
458
+ "weighted avg 0.99 0.99 0.99 2358\n",
459
+ "\n"
460
+ ]
461
+ }
462
+ ],
463
+ "source": [
464
+ "accuracy = accuracy_score(y_test, y_pred)\n",
465
+ "f1 = f1_score(y_test, y_pred, average='weighted')\n",
466
+ "loss = log_loss(y_test, y_prob)\n",
467
+ "\n",
468
+ "print('Accuracy:', round(accuracy, 4))\n",
469
+ "print('Weighted F1 Score:', round(f1, 4))\n",
470
+ "print('Log Loss:', round(loss, 4))\n",
471
+ "\n",
472
+ "pred_labels = label_encoder.inverse_transform(y_pred)\n",
473
+ "true_labels = label_encoder.inverse_transform(y_test)\n",
474
+ "\n",
475
+ "print('\\nClassification Report:\\n')\n",
476
+ "print(classification_report(true_labels, pred_labels))"
477
+ ]
478
+ },
479
+ {
480
+ "cell_type": "markdown",
481
+ "id": "6030171e",
482
+ "metadata": {},
483
+ "source": [
484
+ "## Step 6: Plot Confusion Matrix"
485
+ ]
486
+ },
487
+ {
488
+ "cell_type": "code",
489
+ "execution_count": 35,
490
+ "id": "017aecb3",
491
+ "metadata": {},
492
+ "outputs": [
493
+ {
494
+ "data": {
495
+ "image/png": "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",
496
+ "text/plain": [
497
+ "<Figure size 600x500 with 2 Axes>"
498
+ ]
499
+ },
500
+ "metadata": {},
501
+ "output_type": "display_data"
502
+ }
503
+ ],
504
+ "source": [
505
+ "cm = confusion_matrix(y_test, y_pred)\n",
506
+ "\n",
507
+ "plt.figure(figsize=(6, 5))\n",
508
+ "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n",
509
+ " xticklabels=label_encoder.classes_,\n",
510
+ " yticklabels=label_encoder.classes_)\n",
511
+ "plt.xlabel('Predicted')\n",
512
+ "plt.ylabel('Actual')\n",
513
+ "plt.title('Confusion Matrix')\n",
514
+ "plt.tight_layout()\n",
515
+ "plt.show()"
516
+ ]
517
+ },
518
+ {
519
+ "cell_type": "markdown",
520
+ "id": "dacb3094",
521
+ "metadata": {},
522
+ "source": [
523
+ "## Step 7: Save Final Predictions"
524
+ ]
525
+ },
526
+ {
527
+ "cell_type": "code",
528
+ "execution_count": 36,
529
+ "id": "51e06c3f",
530
+ "metadata": {},
531
+ "outputs": [
532
+ {
533
+ "data": {
534
+ "text/html": [
535
+ "<div>\n",
536
+ "<style scoped>\n",
537
+ " .dataframe tbody tr th:only-of-type {\n",
538
+ " vertical-align: middle;\n",
539
+ " }\n",
540
+ "\n",
541
+ " .dataframe tbody tr th {\n",
542
+ " vertical-align: top;\n",
543
+ " }\n",
544
+ "\n",
545
+ " .dataframe thead th {\n",
546
+ " text-align: right;\n",
547
+ " }\n",
548
+ "</style>\n",
549
+ "<table border=\"1\" class=\"dataframe\">\n",
550
+ " <thead>\n",
551
+ " <tr style=\"text-align: right;\">\n",
552
+ " <th></th>\n",
553
+ " <th>Actual</th>\n",
554
+ " <th>Predicted</th>\n",
555
+ " </tr>\n",
556
+ " </thead>\n",
557
+ " <tbody>\n",
558
+ " <tr>\n",
559
+ " <th>0</th>\n",
560
+ " <td>Store</td>\n",
561
+ " <td>Store</td>\n",
562
+ " </tr>\n",
563
+ " <tr>\n",
564
+ " <th>1</th>\n",
565
+ " <td>Store</td>\n",
566
+ " <td>Store</td>\n",
567
+ " </tr>\n",
568
+ " <tr>\n",
569
+ " <th>2</th>\n",
570
+ " <td>Store</td>\n",
571
+ " <td>Store</td>\n",
572
+ " </tr>\n",
573
+ " <tr>\n",
574
+ " <th>3</th>\n",
575
+ " <td>Store</td>\n",
576
+ " <td>Store</td>\n",
577
+ " </tr>\n",
578
+ " <tr>\n",
579
+ " <th>4</th>\n",
580
+ " <td>Store</td>\n",
581
+ " <td>Store</td>\n",
582
+ " </tr>\n",
583
+ " <tr>\n",
584
+ " <th>5</th>\n",
585
+ " <td>Store</td>\n",
586
+ " <td>Store</td>\n",
587
+ " </tr>\n",
588
+ " <tr>\n",
589
+ " <th>6</th>\n",
590
+ " <td>Store</td>\n",
591
+ " <td>Store</td>\n",
592
+ " </tr>\n",
593
+ " <tr>\n",
594
+ " <th>7</th>\n",
595
+ " <td>Store</td>\n",
596
+ " <td>Store</td>\n",
597
+ " </tr>\n",
598
+ " <tr>\n",
599
+ " <th>8</th>\n",
600
+ " <td>Store</td>\n",
601
+ " <td>Store</td>\n",
602
+ " </tr>\n",
603
+ " <tr>\n",
604
+ " <th>9</th>\n",
605
+ " <td>Store</td>\n",
606
+ " <td>Store</td>\n",
607
+ " </tr>\n",
608
+ " <tr>\n",
609
+ " <th>10</th>\n",
610
+ " <td>Online</td>\n",
611
+ " <td>Online</td>\n",
612
+ " </tr>\n",
613
+ " <tr>\n",
614
+ " <th>11</th>\n",
615
+ " <td>Online</td>\n",
616
+ " <td>Online</td>\n",
617
+ " </tr>\n",
618
+ " <tr>\n",
619
+ " <th>12</th>\n",
620
+ " <td>Store</td>\n",
621
+ " <td>Store</td>\n",
622
+ " </tr>\n",
623
+ " <tr>\n",
624
+ " <th>13</th>\n",
625
+ " <td>Store</td>\n",
626
+ " <td>Store</td>\n",
627
+ " </tr>\n",
628
+ " <tr>\n",
629
+ " <th>14</th>\n",
630
+ " <td>Store</td>\n",
631
+ " <td>Store</td>\n",
632
+ " </tr>\n",
633
+ " </tbody>\n",
634
+ "</table>\n",
635
+ "</div>"
636
+ ],
637
+ "text/plain": [
638
+ " Actual Predicted\n",
639
+ "0 Store Store\n",
640
+ "1 Store Store\n",
641
+ "2 Store Store\n",
642
+ "3 Store Store\n",
643
+ "4 Store Store\n",
644
+ "5 Store Store\n",
645
+ "6 Store Store\n",
646
+ "7 Store Store\n",
647
+ "8 Store Store\n",
648
+ "9 Store Store\n",
649
+ "10 Online Online\n",
650
+ "11 Online Online\n",
651
+ "12 Store Store\n",
652
+ "13 Store Store\n",
653
+ "14 Store Store"
654
+ ]
655
+ },
656
+ "metadata": {},
657
+ "output_type": "display_data"
658
+ },
659
+ {
660
+ "name": "stdout",
661
+ "output_type": "stream",
662
+ "text": [
663
+ "Saved file: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends\\metrics\\prediction.csv\n"
664
+ ]
665
+ }
666
+ ],
667
+ "source": [
668
+ "results = pd.DataFrame({\n",
669
+ " 'Actual': true_labels,\n",
670
+ " 'Predicted': pred_labels\n",
671
+ "})\n",
672
+ "\n",
673
+ "display(results.head(15))\n",
674
+ "\n",
675
+ "base_output = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends'\n",
676
+ "model_dir = os.path.join(base_output, 'model')\n",
677
+ "metrics_dir = os.path.join(base_output, 'metrics')\n",
678
+ "\n",
679
+ "os.makedirs(model_dir, exist_ok=True)\n",
680
+ "os.makedirs(metrics_dir, exist_ok=True)\n",
681
+ "\n",
682
+ "save_path = os.path.join(metrics_dir, 'prediction.csv')\n",
683
+ "results.to_csv(save_path, index=False)\n",
684
+ "print('Saved file:', save_path)"
685
+ ]
686
+ },
687
+ {
688
+ "cell_type": "markdown",
689
+ "id": "f5938c3f",
690
+ "metadata": {},
691
+ "source": [
692
+ "## Step 8: Final Summary"
693
+ ]
694
+ },
695
+ {
696
+ "cell_type": "code",
697
+ "execution_count": 37,
698
+ "id": "86f32c22",
699
+ "metadata": {},
700
+ "outputs": [
701
+ {
702
+ "data": {
703
+ "text/html": [
704
+ "<div>\n",
705
+ "<style scoped>\n",
706
+ " .dataframe tbody tr th:only-of-type {\n",
707
+ " vertical-align: middle;\n",
708
+ " }\n",
709
+ "\n",
710
+ " .dataframe tbody tr th {\n",
711
+ " vertical-align: top;\n",
712
+ " }\n",
713
+ "\n",
714
+ " .dataframe thead th {\n",
715
+ " text-align: right;\n",
716
+ " }\n",
717
+ "</style>\n",
718
+ "<table border=\"1\" class=\"dataframe\">\n",
719
+ " <thead>\n",
720
+ " <tr style=\"text-align: right;\">\n",
721
+ " <th></th>\n",
722
+ " <th>Metric</th>\n",
723
+ " <th>Value</th>\n",
724
+ " </tr>\n",
725
+ " </thead>\n",
726
+ " <tbody>\n",
727
+ " <tr>\n",
728
+ " <th>0</th>\n",
729
+ " <td>Accuracy</td>\n",
730
+ " <td>0.988126</td>\n",
731
+ " </tr>\n",
732
+ " <tr>\n",
733
+ " <th>1</th>\n",
734
+ " <td>Weighted F1</td>\n",
735
+ " <td>0.988326</td>\n",
736
+ " </tr>\n",
737
+ " <tr>\n",
738
+ " <th>2</th>\n",
739
+ " <td>Log Loss</td>\n",
740
+ " <td>0.023689</td>\n",
741
+ " </tr>\n",
742
+ " </tbody>\n",
743
+ "</table>\n",
744
+ "</div>"
745
+ ],
746
+ "text/plain": [
747
+ " Metric Value\n",
748
+ "0 Accuracy 0.988126\n",
749
+ "1 Weighted F1 0.988326\n",
750
+ "2 Log Loss 0.023689"
751
+ ]
752
+ },
753
+ "metadata": {},
754
+ "output_type": "display_data"
755
+ },
756
+ {
757
+ "name": "stdout",
758
+ "output_type": "stream",
759
+ "text": [
760
+ "Target classes: ['Hybrid', 'Online', 'Store']\n",
761
+ "Saved model: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends\\model\\model.joblib\n",
762
+ "Saved columns: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends\\model\\feature_columns.json\n",
763
+ "Saved labels: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends\\model\\label_classes.json\n",
764
+ "Saved target info: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends\\model\\target_info.json\n",
765
+ "Saved metrics: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends\\metrics\\final_metircs.csv\n"
766
+ ]
767
+ }
768
+ ],
769
+ "source": [
770
+ "summary = pd.DataFrame([\n",
771
+ " {'Metric': 'Accuracy', 'Value': accuracy},\n",
772
+ " {'Metric': 'Weighted F1', 'Value': f1},\n",
773
+ " {'Metric': 'Log Loss', 'Value': loss}\n",
774
+ "])\n",
775
+ "\n",
776
+ "display(summary)\n",
777
+ "print('Target classes:', list(label_encoder.classes_))\n",
778
+ "\n",
779
+ "base_output = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Consumer_Shopping_Trends'\n",
780
+ "model_dir = os.path.join(base_output, 'model')\n",
781
+ "metrics_dir = os.path.join(base_output, 'metrics')\n",
782
+ "\n",
783
+ "os.makedirs(model_dir, exist_ok=True)\n",
784
+ "os.makedirs(metrics_dir, exist_ok=True)\n",
785
+ "\n",
786
+ "model_path = os.path.join(model_dir, 'model.joblib')\n",
787
+ "columns_path = os.path.join(model_dir, 'feature_columns.json')\n",
788
+ "labels_path = os.path.join(model_dir, 'label_classes.json')\n",
789
+ "info_path = os.path.join(model_dir, 'target_info.json')\n",
790
+ "metrics_path = os.path.join(metrics_dir, 'final_metircs.csv')\n",
791
+ "\n",
792
+ "joblib.dump(model, model_path)\n",
793
+ "with open(columns_path, 'w', encoding='utf-8') as f:\n",
794
+ " json.dump(list(X.columns), f, indent=2)\n",
795
+ "with open(labels_path, 'w', encoding='utf-8') as f:\n",
796
+ " json.dump(list(label_encoder.classes_), f, indent=2)\n",
797
+ "with open(info_path, 'w', encoding='utf-8') as f:\n",
798
+ " json.dump({'target_col': target_column, 'model_name': 'LogisticRegression'}, f, indent=2)\n",
799
+ "summary.to_csv(metrics_path, index=False)\n",
800
+ "\n",
801
+ "print('Saved model:', model_path)\n",
802
+ "print('Saved columns:', columns_path)\n",
803
+ "print('Saved labels:', labels_path)\n",
804
+ "print('Saved target info:', info_path)\n",
805
+ "print('Saved metrics:', metrics_path)"
806
+ ]
807
+ }
808
+ ],
809
+ "metadata": {
810
+ "kernelspec": {
811
+ "display_name": ".venv",
812
+ "language": "python",
813
+ "name": "python3"
814
+ },
815
+ "language_info": {
816
+ "codemirror_mode": {
817
+ "name": "ipython",
818
+ "version": 3
819
+ },
820
+ "file_extension": ".py",
821
+ "mimetype": "text/x-python",
822
+ "name": "python",
823
+ "nbconvert_exporter": "python",
824
+ "pygments_lexer": "ipython3",
825
+ "version": "3.11.9"
826
+ }
827
+ },
828
+ "nbformat": 4,
829
+ "nbformat_minor": 5
830
+ }
notebooks/F1_Strategy.ipynb ADDED
@@ -0,0 +1,627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "e20a1210",
6
+ "metadata": {},
7
+ "source": [
8
+ "## Step 1: Import Libraries"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": 22,
14
+ "id": "7644ff9f",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import os\n",
19
+ "import json\n",
20
+ "import joblib\n",
21
+ "import pandas as pd\n",
22
+ "import matplotlib.pyplot as plt\n",
23
+ "import seaborn as sns\n",
24
+ "\n",
25
+ "from sklearn.model_selection import train_test_split\n",
26
+ "from sklearn.ensemble import RandomForestClassifier\n",
27
+ "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, log_loss, classification_report, confusion_matrix"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "markdown",
32
+ "id": "7d9c169c",
33
+ "metadata": {},
34
+ "source": [
35
+ "## Step 2: Load Dataset"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "code",
40
+ "execution_count": 23,
41
+ "id": "1a6a30cd",
42
+ "metadata": {},
43
+ "outputs": [
44
+ {
45
+ "name": "stdout",
46
+ "output_type": "stream",
47
+ "text": [
48
+ "Dataset shape: (101371, 16)\n"
49
+ ]
50
+ },
51
+ {
52
+ "data": {
53
+ "text/html": [
54
+ "<div>\n",
55
+ "<style scoped>\n",
56
+ " .dataframe tbody tr th:only-of-type {\n",
57
+ " vertical-align: middle;\n",
58
+ " }\n",
59
+ "\n",
60
+ " .dataframe tbody tr th {\n",
61
+ " vertical-align: top;\n",
62
+ " }\n",
63
+ "\n",
64
+ " .dataframe thead th {\n",
65
+ " text-align: right;\n",
66
+ " }\n",
67
+ "</style>\n",
68
+ "<table border=\"1\" class=\"dataframe\">\n",
69
+ " <thead>\n",
70
+ " <tr style=\"text-align: right;\">\n",
71
+ " <th></th>\n",
72
+ " <th>Driver</th>\n",
73
+ " <th>LapNumber</th>\n",
74
+ " <th>Compound</th>\n",
75
+ " <th>Stint</th>\n",
76
+ " <th>TyreLife</th>\n",
77
+ " <th>Position</th>\n",
78
+ " <th>LapTime (s)</th>\n",
79
+ " <th>Race</th>\n",
80
+ " <th>Year</th>\n",
81
+ " <th>LapTime_Delta</th>\n",
82
+ " <th>Cumulative_Degradation</th>\n",
83
+ " <th>PitStop</th>\n",
84
+ " <th>PitNextLap</th>\n",
85
+ " <th>RaceProgress</th>\n",
86
+ " <th>Normalized_TyreLife</th>\n",
87
+ " <th>Position_Change</th>\n",
88
+ " </tr>\n",
89
+ " </thead>\n",
90
+ " <tbody>\n",
91
+ " <tr>\n",
92
+ " <th>0</th>\n",
93
+ " <td>ALB</td>\n",
94
+ " <td>1</td>\n",
95
+ " <td>MEDIUM</td>\n",
96
+ " <td>1</td>\n",
97
+ " <td>2.0</td>\n",
98
+ " <td>17</td>\n",
99
+ " <td>100.625</td>\n",
100
+ " <td>Abu Dhabi Grand Prix</td>\n",
101
+ " <td>2023</td>\n",
102
+ " <td>0.000</td>\n",
103
+ " <td>0.000</td>\n",
104
+ " <td>0</td>\n",
105
+ " <td>0</td>\n",
106
+ " <td>0.017241</td>\n",
107
+ " <td>0.117647</td>\n",
108
+ " <td>0.0</td>\n",
109
+ " </tr>\n",
110
+ " <tr>\n",
111
+ " <th>1</th>\n",
112
+ " <td>ALB</td>\n",
113
+ " <td>2</td>\n",
114
+ " <td>MEDIUM</td>\n",
115
+ " <td>1</td>\n",
116
+ " <td>3.0</td>\n",
117
+ " <td>18</td>\n",
118
+ " <td>93.560</td>\n",
119
+ " <td>Abu Dhabi Grand Prix</td>\n",
120
+ " <td>2023</td>\n",
121
+ " <td>-7.065</td>\n",
122
+ " <td>-7.065</td>\n",
123
+ " <td>0</td>\n",
124
+ " <td>0</td>\n",
125
+ " <td>0.034483</td>\n",
126
+ " <td>0.176471</td>\n",
127
+ " <td>-1.0</td>\n",
128
+ " </tr>\n",
129
+ " <tr>\n",
130
+ " <th>2</th>\n",
131
+ " <td>ALB</td>\n",
132
+ " <td>3</td>\n",
133
+ " <td>MEDIUM</td>\n",
134
+ " <td>1</td>\n",
135
+ " <td>4.0</td>\n",
136
+ " <td>18</td>\n",
137
+ " <td>91.768</td>\n",
138
+ " <td>Abu Dhabi Grand Prix</td>\n",
139
+ " <td>2023</td>\n",
140
+ " <td>-1.792</td>\n",
141
+ " <td>-8.857</td>\n",
142
+ " <td>0</td>\n",
143
+ " <td>0</td>\n",
144
+ " <td>0.051724</td>\n",
145
+ " <td>0.235294</td>\n",
146
+ " <td>0.0</td>\n",
147
+ " </tr>\n",
148
+ " <tr>\n",
149
+ " <th>3</th>\n",
150
+ " <td>ALB</td>\n",
151
+ " <td>4</td>\n",
152
+ " <td>MEDIUM</td>\n",
153
+ " <td>1</td>\n",
154
+ " <td>5.0</td>\n",
155
+ " <td>18</td>\n",
156
+ " <td>91.591</td>\n",
157
+ " <td>Abu Dhabi Grand Prix</td>\n",
158
+ " <td>2023</td>\n",
159
+ " <td>-0.177</td>\n",
160
+ " <td>-9.034</td>\n",
161
+ " <td>0</td>\n",
162
+ " <td>0</td>\n",
163
+ " <td>0.068966</td>\n",
164
+ " <td>0.294118</td>\n",
165
+ " <td>0.0</td>\n",
166
+ " </tr>\n",
167
+ " <tr>\n",
168
+ " <th>4</th>\n",
169
+ " <td>ALB</td>\n",
170
+ " <td>5</td>\n",
171
+ " <td>MEDIUM</td>\n",
172
+ " <td>1</td>\n",
173
+ " <td>6.0</td>\n",
174
+ " <td>18</td>\n",
175
+ " <td>91.422</td>\n",
176
+ " <td>Abu Dhabi Grand Prix</td>\n",
177
+ " <td>2023</td>\n",
178
+ " <td>-0.169</td>\n",
179
+ " <td>-9.203</td>\n",
180
+ " <td>0</td>\n",
181
+ " <td>0</td>\n",
182
+ " <td>0.086207</td>\n",
183
+ " <td>0.352941</td>\n",
184
+ " <td>0.0</td>\n",
185
+ " </tr>\n",
186
+ " </tbody>\n",
187
+ "</table>\n",
188
+ "</div>"
189
+ ],
190
+ "text/plain": [
191
+ " Driver LapNumber Compound Stint TyreLife Position LapTime (s) \\\n",
192
+ "0 ALB 1 MEDIUM 1 2.0 17 100.625 \n",
193
+ "1 ALB 2 MEDIUM 1 3.0 18 93.560 \n",
194
+ "2 ALB 3 MEDIUM 1 4.0 18 91.768 \n",
195
+ "3 ALB 4 MEDIUM 1 5.0 18 91.591 \n",
196
+ "4 ALB 5 MEDIUM 1 6.0 18 91.422 \n",
197
+ "\n",
198
+ " Race Year LapTime_Delta Cumulative_Degradation PitStop \\\n",
199
+ "0 Abu Dhabi Grand Prix 2023 0.000 0.000 0 \n",
200
+ "1 Abu Dhabi Grand Prix 2023 -7.065 -7.065 0 \n",
201
+ "2 Abu Dhabi Grand Prix 2023 -1.792 -8.857 0 \n",
202
+ "3 Abu Dhabi Grand Prix 2023 -0.177 -9.034 0 \n",
203
+ "4 Abu Dhabi Grand Prix 2023 -0.169 -9.203 0 \n",
204
+ "\n",
205
+ " PitNextLap RaceProgress Normalized_TyreLife Position_Change \n",
206
+ "0 0 0.017241 0.117647 0.0 \n",
207
+ "1 0 0.034483 0.176471 -1.0 \n",
208
+ "2 0 0.051724 0.235294 0.0 \n",
209
+ "3 0 0.068966 0.294118 0.0 \n",
210
+ "4 0 0.086207 0.352941 0.0 "
211
+ ]
212
+ },
213
+ "metadata": {},
214
+ "output_type": "display_data"
215
+ },
216
+ {
217
+ "name": "stdout",
218
+ "output_type": "stream",
219
+ "text": [
220
+ "PitNextLap\n",
221
+ "0 75542\n",
222
+ "1 25829\n",
223
+ "Name: count, dtype: int64\n"
224
+ ]
225
+ }
226
+ ],
227
+ "source": [
228
+ "data_path = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\dataset\\f1_strategy_dataset_v4.csv'\n",
229
+ "df = pd.read_csv(data_path)\n",
230
+ "\n",
231
+ "print('Dataset shape:', df.shape)\n",
232
+ "display(df.head())\n",
233
+ "\n",
234
+ "target_column = 'PitNextLap'\n",
235
+ "print(df[target_column].value_counts())"
236
+ ]
237
+ },
238
+ {
239
+ "cell_type": "markdown",
240
+ "id": "91763788",
241
+ "metadata": {},
242
+ "source": [
243
+ "## Step 3: Prepare Data"
244
+ ]
245
+ },
246
+ {
247
+ "cell_type": "code",
248
+ "execution_count": 24,
249
+ "id": "d1e0e470",
250
+ "metadata": {},
251
+ "outputs": [
252
+ {
253
+ "name": "stdout",
254
+ "output_type": "stream",
255
+ "text": [
256
+ "X_train shape: (81096, 76)\n",
257
+ "X_test shape: (20275, 76)\n",
258
+ "Class distribution in train:\n",
259
+ "PitNextLap\n",
260
+ "0 0.745203\n",
261
+ "1 0.254797\n",
262
+ "Name: proportion, dtype: float64\n"
263
+ ]
264
+ }
265
+ ],
266
+ "source": [
267
+ "X = df.drop(columns=[target_column])\n",
268
+ "y = df[target_column].astype(int)\n",
269
+ "\n",
270
+ "X = pd.get_dummies(X, drop_first=False)\n",
271
+ "\n",
272
+ "X_train, X_test, y_train, y_test = train_test_split(\n",
273
+ " X,\n",
274
+ " y,\n",
275
+ " test_size=0.2,\n",
276
+ " random_state=21,\n",
277
+ " stratify=y\n",
278
+ ")\n",
279
+ "\n",
280
+ "print('X_train shape:', X_train.shape)\n",
281
+ "print('X_test shape:', X_test.shape)\n",
282
+ "print('Class distribution in train:')\n",
283
+ "print(y_train.value_counts(normalize=True).sort_index())"
284
+ ]
285
+ },
286
+ {
287
+ "cell_type": "markdown",
288
+ "id": "425c5bd4",
289
+ "metadata": {},
290
+ "source": [
291
+ "## Step 4: Train RandomForest Model"
292
+ ]
293
+ },
294
+ {
295
+ "cell_type": "code",
296
+ "execution_count": 25,
297
+ "id": "95540597",
298
+ "metadata": {},
299
+ "outputs": [
300
+ {
301
+ "name": "stdout",
302
+ "output_type": "stream",
303
+ "text": [
304
+ "Model training completed\n"
305
+ ]
306
+ }
307
+ ],
308
+ "source": [
309
+ "model = RandomForestClassifier(\n",
310
+ " n_estimators=150,\n",
311
+ " random_state=21,\n",
312
+ " n_jobs=-1,\n",
313
+ " class_weight='balanced_subsample'\n",
314
+ ")\n",
315
+ "\n",
316
+ "model.fit(X_train, y_train)\n",
317
+ "\n",
318
+ "y_pred = model.predict(X_test)\n",
319
+ "y_prob = model.predict_proba(X_test)[:, 1]\n",
320
+ "\n",
321
+ "print('Model training completed')"
322
+ ]
323
+ },
324
+ {
325
+ "cell_type": "markdown",
326
+ "id": "a0b9980f",
327
+ "metadata": {},
328
+ "source": [
329
+ "## Step 5: Check Model Performance"
330
+ ]
331
+ },
332
+ {
333
+ "cell_type": "code",
334
+ "execution_count": 26,
335
+ "id": "3316a60c",
336
+ "metadata": {},
337
+ "outputs": [
338
+ {
339
+ "name": "stdout",
340
+ "output_type": "stream",
341
+ "text": [
342
+ "Accuracy: 0.9694\n",
343
+ "Precision: 0.9729\n",
344
+ "Recall: 0.905\n",
345
+ "F1 Score: 0.9377\n",
346
+ "ROC AUC: 0.9951\n",
347
+ "Log Loss: 0.1264\n",
348
+ "\n",
349
+ "Classification Report:\n",
350
+ "\n",
351
+ " precision recall f1-score support\n",
352
+ "\n",
353
+ " 0 0.9683 0.9914 0.9797 15109\n",
354
+ " 1 0.9729 0.9050 0.9377 5166\n",
355
+ "\n",
356
+ " accuracy 0.9694 20275\n",
357
+ " macro avg 0.9706 0.9482 0.9587 20275\n",
358
+ "weighted avg 0.9695 0.9694 0.9690 20275\n",
359
+ "\n"
360
+ ]
361
+ }
362
+ ],
363
+ "source": [
364
+ "accuracy = accuracy_score(y_test, y_pred)\n",
365
+ "precision = precision_score(y_test, y_pred, zero_division=0)\n",
366
+ "recall = recall_score(y_test, y_pred, zero_division=0)\n",
367
+ "f1 = f1_score(y_test, y_pred, zero_division=0)\n",
368
+ "roc_auc = roc_auc_score(y_test, y_prob)\n",
369
+ "loss = log_loss(y_test, y_prob)\n",
370
+ "\n",
371
+ "print('Accuracy:', round(accuracy, 4))\n",
372
+ "print('Precision:', round(precision, 4))\n",
373
+ "print('Recall:', round(recall, 4))\n",
374
+ "print('F1 Score:', round(f1, 4))\n",
375
+ "print('ROC AUC:', round(roc_auc, 4))\n",
376
+ "print('Log Loss:', round(loss, 4))\n",
377
+ "\n",
378
+ "print('\\nClassification Report:\\n')\n",
379
+ "print(classification_report(y_test, y_pred, digits=4, zero_division=0))"
380
+ ]
381
+ },
382
+ {
383
+ "cell_type": "markdown",
384
+ "id": "a99818e1",
385
+ "metadata": {},
386
+ "source": [
387
+ "## Step 6: Plot Confusion Matrix"
388
+ ]
389
+ },
390
+ {
391
+ "cell_type": "code",
392
+ "execution_count": 27,
393
+ "id": "89658dd3",
394
+ "metadata": {},
395
+ "outputs": [
396
+ {
397
+ "data": {
398
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkYAAAHqCAYAAADh64FkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAARRlJREFUeJzt3QmcTXUbwPFnxjDGMvYZlK0SJrKGKWuESBTJEsoW2XeTJaSmSPZMqjcqIookayRlJ/syESXJFmYayxjmvp/n3723uWNoRmdmnJnf9/M57733nP8999x7X3k8z//5Xy+Hw+EQAAAAiDefAQAAwN8IjAAAAJwIjAAAAJwIjAAAAJwIjAAAAJwIjAAAAJwIjAAAAJwIjAAAAJwIjAAAAJwIjIA7xKFDh6RevXqSI0cO8fLykkWLFll6/l9++cWcd+bMmZae185q1aplNgBwITAC4vj555/lxRdflHvuuUcyZ84s/v7+8sgjj8ikSZPk8uXLyfpZtW/fXvbs2SOvvfaafPzxx1KpUqU08908//zzJijTzzOhz1GDQj2u21tvvZXk8584cUJGjhwpO3futOiKAaRXPql9AcCd4uuvv5ZnnnlGfH19pV27dlK6dGm5evWq/PDDDzJw4EDZt2+fzJgxI1leW4OFjRs3ytChQ6VHjx7J8hpFihQxr5MxY0ZJDT4+PnLp0iX56quvpEWLFh7HZs+ebQLRK1eu3Na5NTAaNWqUFC1aVMqVK5fo561cufK2Xg9A2kVgBIjI0aNHpWXLliZ4WLNmjRQoUMD9uXTv3l0OHz5sAqfkcubMGXObM2fOZHsNzcZo8JFaNODU7Nunn356Q2A0Z84cadSokXz++ecpci0aoGXJkkUyZcqUIq8HwD4opQEiMnbsWImKipIPPvjAIyhyue+++6R3797ux9euXZNXX31V7r33XvMXvmYqXn75ZYmOjvZ4nu5/4oknTNapcuXKJjDRMt1HH33kHqMlIA3IlGamNIDR57lKUK77celzdFxcq1atkmrVqpngKlu2bFKiRAlzTf82x0gDwerVq0vWrFnNc5s0aSIHDhxI8PU0QNRr0nE6F+qFF14wQUZitW7dWpYtWyYXLlxw79u6dasppemx+M6dOycDBgyQMmXKmPekpbjHH39cdu3a5R6zdu1aeeihh8x9vR5XSc71PnUOkWb/tm/fLjVq1DABketziT/HSMuZ+h3Ff//169eXXLlymcwUgLSNwAgQMeUdDVgefvjhRH0enTp1khEjRkiFChVkwoQJUrNmTQkNDTVZp/g0mGjevLk89thjMn78ePMXrAYXWppTTz/9tDmHatWqlZlfNHHixCR9L3ouDcA0MBs9erR5nSeffFLWr19/y+d988035i/906dPm+CnX79+smHDBpPZ0UAqPs30/PXXX+a96n0NPrSElVj6XjVo+eKLLzyyRSVLljSfZXxHjhwxk9D1vb399tsmcNR5WPp5u4KUUqVKmfesunTpYj4/3TQIcvnzzz9NQKVlNv1sa9euneD16VyyfPnymQDp+vXrZt+7775rSm5TpkyRggULJvq9ArApB5DORUREOPSPQpMmTRI1fufOnWZ8p06dPPYPGDDA7F+zZo17X5EiRcy+devWufedPn3a4evr6+jfv79739GjR824cePGeZyzffv25hzxvfLKK2a8y4QJE8zjM2fO3PS6Xa/x4YcfuveVK1fOERAQ4Pjzzz/d+3bt2uXw9vZ2tGvX7obX69Chg8c5n3rqKUeePHlu+ppx30fWrFnN/ebNmzvq1Klj7l+/ft2RP39+x6hRoxL8DK5cuWLGxH8f+vmNHj3avW/r1q03vDeXmjVrmmNhYWEJHtMtrhUrVpjxY8aMcRw5csSRLVs2R9OmTf/1PQJIG8gYId2LjIw0n0H27NkT9VksXbrU3Gp2Ja7+/fub2/hzkYKCgkypykUzElrm0myIVVxzk7788kuJjY1N1HP++OMP08Wl2avcuXO79z/44IMmu+V6n3F17drV47G+L83GuD7DxNCSmZa/Tp48acp4eptQGU1pmdLb++//TGkGR1/LVSb88ccfE/2aeh4tsyWGLpmgnYmahdIMl5bWNGsEIH0gMEK6p/NWlJaIEuPXX381f1nrvKO48ufPbwIUPR5X4cKFbziHltPOnz9v2Wf/7LPPmvKXlvgCAwNNSe+zzz67ZZDkuk4NMuLT8tTZs2fl4sWLt3wv+j5UUt5Lw4YNTRA6b948042m84Pif5Yuev1aZixevLgJbvLmzWsCy927d0tERESiX/Ouu+5K0kRrXTJAg0UNHCdPniwBAQGJfi4AeyMwQrqngZHOHdm7d2+SPov4k59vJkOGDAnudzgct/0arvkvLn5+frJu3TozZ6ht27YmcNBg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v36mK+3HH3+UsmXLSv369eX06dN8E0AiXLx40fy50X9gALAeXWlIUZoheuihh2Tq1KnmcWxsrBQqVEh69uwpQ4YM4dsAkkAzRgsXLpSmTZvyuQEWIWOEFHP16lXZvn271K1b95//A3p7m8cbN27kmwAApDoCI6SYs2fPyvXr1yUwMNBjvz4+efIk3wQAINURGAEAADgRGCHF5M2bVzJkyCCnTp3y2K+P8+fPzzcBAEh1BEZIMZkyZZKKFSvK6tWr3ft08rU+Dg4O5psAAKQ6n9S+AKQv2qrfvn17qVSpklSuXFkmTpxo2o9feOGF1L40wBaioqLk8OHD7sdHjx6VnTt3Su7cuaVw4cKpem1AWkC7PlKctuqPGzfOTLguV66cTJ482bTxA/h3a9euldq1a9+wX//BMXPmTD5C4D8iMAIAAHBijhEAAIATgREAAIATgREAAIATgREAAIATgREAAIATgREAAIATgREAAIATgREAAIATgRGQzj3//PPStGlT9+NatWpJnz59UmVFZy8vL7lw4UKKvzYAuBAYAXdwwKKBgm76A7z33XefjB49Wq5du5asr/vFF1/Iq6++mqixBDMA0hp+RBa4gzVo0EA+/PBDiY6OlqVLl0r37t0lY8aMEhIS4jHu6tWrJniygv4YKQCkV2SMgDuYr6+v5M+fX4oUKSLdunWTunXryuLFi93lr9dee00KFiwoJUqUMON/++03adGiheTMmdMEOE2aNJFffvnFfb7r169Lv379zPE8efLIoEGDxOFweLxm/FKaBmWDBw+WQoUKmevRzNUHH3xgzuv6MdNcuXKZzJZel4qNjZXQ0FApVqyY+Pn5SdmyZWXBggUer6OB3v3332+O63niXicApBYCI8BGNIjQ7JBavXq1hIeHy6pVq2TJkiUSExMj9evXl+zZs8v3338v69evl2zZspmsk+s548ePN7/A/r///U9++OEHOXfunCxcuPCWr9muXTv59NNPZfLkyXLgwAF59913zXk1UPr888/NGL2OP/74QyZNmmQea1D00UcfSVhYmOzbt0/69u0rzz33nHz33XfuAO7pp5+Wxo0by86dO6VTp04yZMiQZP70ACARHADuSO3bt3c0adLE3I+NjXWsWrXK4evr6xgwYIA5FhgY6IiOjnaP//jjjx0lSpQwY130uJ+fn2PFihXmcYECBRxjx451H4+JiXHcfffd7tdRNWvWdPTu3dvcDw8P13SSee2EfPvtt+b4+fPn3fuuXLniyJIli2PDhg0eYzt27Oho1aqVuR8SEuIICgryOD548OAbzgUAKY05RsAdTDNBmp3RbJCWp1q3bi0jR440c43KlCnjMa9o165dcvjwYZMxiuvKlSvy888/S0REhMnqVKlSxX3Mx8dHKlWqdEM5zUWzORkyZJCaNWsm+pr1Gi5duiSPPfaYx37NWpUvX97c18xT3OtQwcHBiX4NAEguBEbAHUzn3kyfPt0EQDqXSAMZl6xZs3qMjYqKkooVK8rs2bNvOE++fPluu3SXVHod6uuvv5a77rrL45jOUQKAOxmBEXAH0+BHJzsnRoUKFWTevHkSEBAg/v7+CY4pUKCAbN68WWrUqGEea+v/9u3bzXMTolkpzVTp3CCd+B2fK2Olk7pdgoKCTAB07Nixm2aaSpUqZSaRx7Vp06ZEvU8ASE5MvgbSiDZt2kjevHlNJ5pOvj569KhZZ6hXr15y/PhxM6Z3797yxhtvyKJFi+TgwYPy0ksv3XJBxaJFi0r79u2lQ4cO5jmuc3722WfmuHbLaTealvzOnDljskVayhswYICZcD1r1ixTxvvxxx9lypQp5rHq2rWrHDp0SAYOHGgmbs+ZM8dMCgeA1EZgBKQRWbJkkXXr1knhwoVNx5dmZTp27GjmGLkySP3795e2bduaYEfn9GgQ89RTT93yvFrKa968uQmiSpYsKZ07d5aLFy+aY1oqGzVqlOkoCwwMlB49epj9ukDk8OHDTXeaXod2xmlpTdv3lV6jdrRpsKWt/Nq99vrrryf7ZwQA/8ZLZ2D/6ygAAIB0gIwRAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACAE4ERAACA/O3/eNkL5+0vWWgAAAAASUVORK5CYII=",
399
+ "text/plain": [
400
+ "<Figure size 600x500 with 2 Axes>"
401
+ ]
402
+ },
403
+ "metadata": {},
404
+ "output_type": "display_data"
405
+ }
406
+ ],
407
+ "source": [
408
+ "cm = confusion_matrix(y_test, y_pred)\n",
409
+ "\n",
410
+ "plt.figure(figsize=(6, 5))\n",
411
+ "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\n",
412
+ "plt.xlabel('Predicted')\n",
413
+ "plt.ylabel('Actual')\n",
414
+ "plt.title('Confusion Matrix')\n",
415
+ "plt.tight_layout()\n",
416
+ "plt.show()"
417
+ ]
418
+ },
419
+ {
420
+ "cell_type": "markdown",
421
+ "id": "d710186f",
422
+ "metadata": {},
423
+ "source": [
424
+ "## Step 7: Save Model and Metrics"
425
+ ]
426
+ },
427
+ {
428
+ "cell_type": "code",
429
+ "execution_count": 28,
430
+ "id": "e2060814",
431
+ "metadata": {},
432
+ "outputs": [
433
+ {
434
+ "name": "stdout",
435
+ "output_type": "stream",
436
+ "text": [
437
+ "Saved model: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy\\model\\model.joblib\n",
438
+ "Saved columns: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy\\model\\feature_columns.json\n",
439
+ "Saved labels: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy\\model\\label_classes.json\n",
440
+ "Saved target info: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy\\model\\target_info.json\n",
441
+ "Saved metrics: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy\\metrics\\final_metircs.csv\n",
442
+ "Saved predictions: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy\\metrics\\prediction.csv\n"
443
+ ]
444
+ }
445
+ ],
446
+ "source": [
447
+ "base_output = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\F1_Strategy'\n",
448
+ "model_dir = os.path.join(base_output, 'model')\n",
449
+ "metrics_dir = os.path.join(base_output, 'metrics')\n",
450
+ "\n",
451
+ "os.makedirs(model_dir, exist_ok=True)\n",
452
+ "os.makedirs(metrics_dir, exist_ok=True)\n",
453
+ "\n",
454
+ "model_path = os.path.join(model_dir, 'model.joblib')\n",
455
+ "columns_path = os.path.join(model_dir, 'feature_columns.json')\n",
456
+ "labels_path = os.path.join(model_dir, 'label_classes.json')\n",
457
+ "info_path = os.path.join(model_dir, 'target_info.json')\n",
458
+ "metrics_path = os.path.join(metrics_dir, 'final_metircs.csv')\n",
459
+ "predictions_path = os.path.join(metrics_dir, 'prediction.csv')\n",
460
+ "\n",
461
+ "joblib.dump(model, model_path)\n",
462
+ "with open(columns_path, 'w', encoding='utf-8') as f:\n",
463
+ " json.dump(list(X.columns), f, indent=2)\n",
464
+ "with open(labels_path, 'w', encoding='utf-8') as f:\n",
465
+ " json.dump([0, 1], f, indent=2)\n",
466
+ "with open(info_path, 'w', encoding='utf-8') as f:\n",
467
+ " json.dump({'target_col': target_column, 'model_name': 'RandomForestClassifier'}, f, indent=2)\n",
468
+ "\n",
469
+ "final_metrics = pd.DataFrame([\n",
470
+ " {'metric': 'accuracy', 'value': accuracy},\n",
471
+ " {'metric': 'precision', 'value': precision},\n",
472
+ " {'metric': 'recall', 'value': recall},\n",
473
+ " {'metric': 'f1', 'value': f1},\n",
474
+ " {'metric': 'roc_auc', 'value': roc_auc},\n",
475
+ " {'metric': 'log_loss', 'value': loss}\n",
476
+ "])\n",
477
+ "final_metrics.to_csv(metrics_path, index=False)\n",
478
+ "\n",
479
+ "prediction_df = pd.DataFrame({\n",
480
+ " 'Actual': y_test,\n",
481
+ " 'Predicted': y_pred,\n",
482
+ " 'Probability': y_prob\n",
483
+ "})\n",
484
+ "prediction_df.to_csv(predictions_path, index=False)\n",
485
+ "\n",
486
+ "print('Saved model:', model_path)\n",
487
+ "print('Saved columns:', columns_path)\n",
488
+ "print('Saved labels:', labels_path)\n",
489
+ "print('Saved target info:', info_path)\n",
490
+ "print('Saved metrics:', metrics_path)\n",
491
+ "print('Saved predictions:', predictions_path)"
492
+ ]
493
+ },
494
+ {
495
+ "cell_type": "markdown",
496
+ "id": "25072de9",
497
+ "metadata": {},
498
+ "source": [
499
+ "## Step 8: Final Summary"
500
+ ]
501
+ },
502
+ {
503
+ "cell_type": "code",
504
+ "execution_count": 29,
505
+ "id": "ebe897d6",
506
+ "metadata": {},
507
+ "outputs": [
508
+ {
509
+ "data": {
510
+ "text/html": [
511
+ "<div>\n",
512
+ "<style scoped>\n",
513
+ " .dataframe tbody tr th:only-of-type {\n",
514
+ " vertical-align: middle;\n",
515
+ " }\n",
516
+ "\n",
517
+ " .dataframe tbody tr th {\n",
518
+ " vertical-align: top;\n",
519
+ " }\n",
520
+ "\n",
521
+ " .dataframe thead th {\n",
522
+ " text-align: right;\n",
523
+ " }\n",
524
+ "</style>\n",
525
+ "<table border=\"1\" class=\"dataframe\">\n",
526
+ " <thead>\n",
527
+ " <tr style=\"text-align: right;\">\n",
528
+ " <th></th>\n",
529
+ " <th>Metric</th>\n",
530
+ " <th>Value</th>\n",
531
+ " </tr>\n",
532
+ " </thead>\n",
533
+ " <tbody>\n",
534
+ " <tr>\n",
535
+ " <th>0</th>\n",
536
+ " <td>Accuracy</td>\n",
537
+ " <td>0.969371</td>\n",
538
+ " </tr>\n",
539
+ " <tr>\n",
540
+ " <th>1</th>\n",
541
+ " <td>Precision</td>\n",
542
+ " <td>0.972945</td>\n",
543
+ " </tr>\n",
544
+ " <tr>\n",
545
+ " <th>2</th>\n",
546
+ " <td>Recall</td>\n",
547
+ " <td>0.904955</td>\n",
548
+ " </tr>\n",
549
+ " <tr>\n",
550
+ " <th>3</th>\n",
551
+ " <td>F1 Score</td>\n",
552
+ " <td>0.937719</td>\n",
553
+ " </tr>\n",
554
+ " <tr>\n",
555
+ " <th>4</th>\n",
556
+ " <td>ROC AUC</td>\n",
557
+ " <td>0.995099</td>\n",
558
+ " </tr>\n",
559
+ " <tr>\n",
560
+ " <th>5</th>\n",
561
+ " <td>Log Loss</td>\n",
562
+ " <td>0.126425</td>\n",
563
+ " </tr>\n",
564
+ " </tbody>\n",
565
+ "</table>\n",
566
+ "</div>"
567
+ ],
568
+ "text/plain": [
569
+ " Metric Value\n",
570
+ "0 Accuracy 0.969371\n",
571
+ "1 Precision 0.972945\n",
572
+ "2 Recall 0.904955\n",
573
+ "3 F1 Score 0.937719\n",
574
+ "4 ROC AUC 0.995099\n",
575
+ "5 Log Loss 0.126425"
576
+ ]
577
+ },
578
+ "metadata": {},
579
+ "output_type": "display_data"
580
+ },
581
+ {
582
+ "name": "stdout",
583
+ "output_type": "stream",
584
+ "text": [
585
+ "Final model used: RandomForestClassifier\n",
586
+ "Target column: PitNextLap\n"
587
+ ]
588
+ }
589
+ ],
590
+ "source": [
591
+ "summary = pd.DataFrame([\n",
592
+ " {'Metric': 'Accuracy', 'Value': accuracy},\n",
593
+ " {'Metric': 'Precision', 'Value': precision},\n",
594
+ " {'Metric': 'Recall', 'Value': recall},\n",
595
+ " {'Metric': 'F1 Score', 'Value': f1},\n",
596
+ " {'Metric': 'ROC AUC', 'Value': roc_auc},\n",
597
+ " {'Metric': 'Log Loss', 'Value': loss}\n",
598
+ "])\n",
599
+ "\n",
600
+ "display(summary)\n",
601
+ "print('Final model used: RandomForestClassifier')\n",
602
+ "print('Target column:', target_column)"
603
+ ]
604
+ }
605
+ ],
606
+ "metadata": {
607
+ "kernelspec": {
608
+ "display_name": ".venv",
609
+ "language": "python",
610
+ "name": "python3"
611
+ },
612
+ "language_info": {
613
+ "codemirror_mode": {
614
+ "name": "ipython",
615
+ "version": 3
616
+ },
617
+ "file_extension": ".py",
618
+ "mimetype": "text/x-python",
619
+ "name": "python",
620
+ "nbconvert_exporter": "python",
621
+ "pygments_lexer": "ipython3",
622
+ "version": "3.11.9"
623
+ }
624
+ },
625
+ "nbformat": 4,
626
+ "nbformat_minor": 5
627
+ }
notebooks/Sleep_Health_And_Daily_Performance.ipynb ADDED
@@ -0,0 +1,775 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "c9920956",
6
+ "metadata": {},
7
+ "source": [
8
+ "## Step 1: Import Libraries"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": 20,
14
+ "id": "d8d06600",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import os\n",
19
+ "import json\n",
20
+ "import joblib\n",
21
+ "import pandas as pd\n",
22
+ "import matplotlib.pyplot as plt\n",
23
+ "import seaborn as sns\n",
24
+ "\n",
25
+ "from sklearn.model_selection import train_test_split\n",
26
+ "from sklearn.preprocessing import LabelEncoder\n",
27
+ "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, log_loss, classification_report, confusion_matrix\n",
28
+ "from xgboost import XGBClassifier"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "markdown",
33
+ "id": "88c9c95d",
34
+ "metadata": {},
35
+ "source": [
36
+ "## Step 2: Load Dataset"
37
+ ]
38
+ },
39
+ {
40
+ "cell_type": "code",
41
+ "execution_count": 21,
42
+ "id": "5a9b209d",
43
+ "metadata": {},
44
+ "outputs": [
45
+ {
46
+ "name": "stdout",
47
+ "output_type": "stream",
48
+ "text": [
49
+ "Dataset shape: (100000, 32)\n"
50
+ ]
51
+ },
52
+ {
53
+ "data": {
54
+ "text/html": [
55
+ "<div>\n",
56
+ "<style scoped>\n",
57
+ " .dataframe tbody tr th:only-of-type {\n",
58
+ " vertical-align: middle;\n",
59
+ " }\n",
60
+ "\n",
61
+ " .dataframe tbody tr th {\n",
62
+ " vertical-align: top;\n",
63
+ " }\n",
64
+ "\n",
65
+ " .dataframe thead th {\n",
66
+ " text-align: right;\n",
67
+ " }\n",
68
+ "</style>\n",
69
+ "<table border=\"1\" class=\"dataframe\">\n",
70
+ " <thead>\n",
71
+ " <tr style=\"text-align: right;\">\n",
72
+ " <th></th>\n",
73
+ " <th>person_id</th>\n",
74
+ " <th>age</th>\n",
75
+ " <th>gender</th>\n",
76
+ " <th>occupation</th>\n",
77
+ " <th>bmi</th>\n",
78
+ " <th>country</th>\n",
79
+ " <th>sleep_duration_hrs</th>\n",
80
+ " <th>sleep_quality_score</th>\n",
81
+ " <th>rem_percentage</th>\n",
82
+ " <th>deep_sleep_percentage</th>\n",
83
+ " <th>sleep_latency_mins</th>\n",
84
+ " <th>wake_episodes_per_night</th>\n",
85
+ " <th>caffeine_mg_before_bed</th>\n",
86
+ " <th>alcohol_units_before_bed</th>\n",
87
+ " <th>screen_time_before_bed_mins</th>\n",
88
+ " <th>exercise_day</th>\n",
89
+ " <th>steps_that_day</th>\n",
90
+ " <th>nap_duration_mins</th>\n",
91
+ " <th>stress_score</th>\n",
92
+ " <th>work_hours_that_day</th>\n",
93
+ " <th>chronotype</th>\n",
94
+ " <th>mental_health_condition</th>\n",
95
+ " <th>heart_rate_resting_bpm</th>\n",
96
+ " <th>sleep_aid_used</th>\n",
97
+ " <th>shift_work</th>\n",
98
+ " <th>room_temperature_celsius</th>\n",
99
+ " <th>weekend_sleep_diff_hrs</th>\n",
100
+ " <th>season</th>\n",
101
+ " <th>day_type</th>\n",
102
+ " <th>cognitive_performance_score</th>\n",
103
+ " <th>sleep_disorder_risk</th>\n",
104
+ " <th>felt_rested</th>\n",
105
+ " </tr>\n",
106
+ " </thead>\n",
107
+ " <tbody>\n",
108
+ " <tr>\n",
109
+ " <th>0</th>\n",
110
+ " <td>1</td>\n",
111
+ " <td>29</td>\n",
112
+ " <td>Female</td>\n",
113
+ " <td>Driver</td>\n",
114
+ " <td>25.7</td>\n",
115
+ " <td>Japan</td>\n",
116
+ " <td>6.19</td>\n",
117
+ " <td>6.6</td>\n",
118
+ " <td>22.5</td>\n",
119
+ " <td>19.3</td>\n",
120
+ " <td>16</td>\n",
121
+ " <td>3</td>\n",
122
+ " <td>0</td>\n",
123
+ " <td>0.0</td>\n",
124
+ " <td>32</td>\n",
125
+ " <td>0</td>\n",
126
+ " <td>6592</td>\n",
127
+ " <td>0</td>\n",
128
+ " <td>4.4</td>\n",
129
+ " <td>10.7</td>\n",
130
+ " <td>Morning</td>\n",
131
+ " <td>Healthy</td>\n",
132
+ " <td>63</td>\n",
133
+ " <td>0</td>\n",
134
+ " <td>0</td>\n",
135
+ " <td>20.1</td>\n",
136
+ " <td>1.84</td>\n",
137
+ " <td>Autumn</td>\n",
138
+ " <td>Weekday</td>\n",
139
+ " <td>73.4</td>\n",
140
+ " <td>Healthy</td>\n",
141
+ " <td>0</td>\n",
142
+ " </tr>\n",
143
+ " <tr>\n",
144
+ " <th>1</th>\n",
145
+ " <td>2</td>\n",
146
+ " <td>55</td>\n",
147
+ " <td>Female</td>\n",
148
+ " <td>Software Engineer</td>\n",
149
+ " <td>22.0</td>\n",
150
+ " <td>USA</td>\n",
151
+ " <td>8.32</td>\n",
152
+ " <td>6.9</td>\n",
153
+ " <td>26.9</td>\n",
154
+ " <td>14.9</td>\n",
155
+ " <td>17</td>\n",
156
+ " <td>4</td>\n",
157
+ " <td>0</td>\n",
158
+ " <td>0.0</td>\n",
159
+ " <td>33</td>\n",
160
+ " <td>1</td>\n",
161
+ " <td>10111</td>\n",
162
+ " <td>8</td>\n",
163
+ " <td>4.0</td>\n",
164
+ " <td>3.0</td>\n",
165
+ " <td>Neutral</td>\n",
166
+ " <td>Healthy</td>\n",
167
+ " <td>52</td>\n",
168
+ " <td>1</td>\n",
169
+ " <td>0</td>\n",
170
+ " <td>18.0</td>\n",
171
+ " <td>0.13</td>\n",
172
+ " <td>Winter</td>\n",
173
+ " <td>Weekend</td>\n",
174
+ " <td>99.4</td>\n",
175
+ " <td>Healthy</td>\n",
176
+ " <td>1</td>\n",
177
+ " </tr>\n",
178
+ " <tr>\n",
179
+ " <th>2</th>\n",
180
+ " <td>3</td>\n",
181
+ " <td>42</td>\n",
182
+ " <td>Male</td>\n",
183
+ " <td>Nurse</td>\n",
184
+ " <td>25.0</td>\n",
185
+ " <td>India</td>\n",
186
+ " <td>3.74</td>\n",
187
+ " <td>1.0</td>\n",
188
+ " <td>20.2</td>\n",
189
+ " <td>16.2</td>\n",
190
+ " <td>26</td>\n",
191
+ " <td>4</td>\n",
192
+ " <td>0</td>\n",
193
+ " <td>2.0</td>\n",
194
+ " <td>89</td>\n",
195
+ " <td>1</td>\n",
196
+ " <td>9222</td>\n",
197
+ " <td>28</td>\n",
198
+ " <td>7.8</td>\n",
199
+ " <td>3.6</td>\n",
200
+ " <td>Neutral</td>\n",
201
+ " <td>Both</td>\n",
202
+ " <td>72</td>\n",
203
+ " <td>0</td>\n",
204
+ " <td>1</td>\n",
205
+ " <td>17.9</td>\n",
206
+ " <td>1.67</td>\n",
207
+ " <td>Spring</td>\n",
208
+ " <td>Weekend</td>\n",
209
+ " <td>2.5</td>\n",
210
+ " <td>Severe</td>\n",
211
+ " <td>0</td>\n",
212
+ " </tr>\n",
213
+ " <tr>\n",
214
+ " <th>3</th>\n",
215
+ " <td>4</td>\n",
216
+ " <td>37</td>\n",
217
+ " <td>Female</td>\n",
218
+ " <td>Student</td>\n",
219
+ " <td>29.5</td>\n",
220
+ " <td>India</td>\n",
221
+ " <td>6.79</td>\n",
222
+ " <td>6.4</td>\n",
223
+ " <td>17.7</td>\n",
224
+ " <td>17.7</td>\n",
225
+ " <td>13</td>\n",
226
+ " <td>4</td>\n",
227
+ " <td>0</td>\n",
228
+ " <td>1.0</td>\n",
229
+ " <td>52</td>\n",
230
+ " <td>1</td>\n",
231
+ " <td>9190</td>\n",
232
+ " <td>40</td>\n",
233
+ " <td>4.9</td>\n",
234
+ " <td>6.7</td>\n",
235
+ " <td>Morning</td>\n",
236
+ " <td>Healthy</td>\n",
237
+ " <td>71</td>\n",
238
+ " <td>0</td>\n",
239
+ " <td>0</td>\n",
240
+ " <td>19.1</td>\n",
241
+ " <td>2.37</td>\n",
242
+ " <td>Summer</td>\n",
243
+ " <td>Weekend</td>\n",
244
+ " <td>67.8</td>\n",
245
+ " <td>Healthy</td>\n",
246
+ " <td>0</td>\n",
247
+ " </tr>\n",
248
+ " <tr>\n",
249
+ " <th>4</th>\n",
250
+ " <td>5</td>\n",
251
+ " <td>23</td>\n",
252
+ " <td>Male</td>\n",
253
+ " <td>Lawyer</td>\n",
254
+ " <td>23.6</td>\n",
255
+ " <td>Spain</td>\n",
256
+ " <td>5.02</td>\n",
257
+ " <td>3.2</td>\n",
258
+ " <td>23.3</td>\n",
259
+ " <td>18.3</td>\n",
260
+ " <td>30</td>\n",
261
+ " <td>5</td>\n",
262
+ " <td>40</td>\n",
263
+ " <td>0.0</td>\n",
264
+ " <td>72</td>\n",
265
+ " <td>0</td>\n",
266
+ " <td>4273</td>\n",
267
+ " <td>0</td>\n",
268
+ " <td>7.4</td>\n",
269
+ " <td>10.4</td>\n",
270
+ " <td>Neutral</td>\n",
271
+ " <td>Healthy</td>\n",
272
+ " <td>71</td>\n",
273
+ " <td>0</td>\n",
274
+ " <td>0</td>\n",
275
+ " <td>19.7</td>\n",
276
+ " <td>1.26</td>\n",
277
+ " <td>Summer</td>\n",
278
+ " <td>Weekday</td>\n",
279
+ " <td>38.1</td>\n",
280
+ " <td>Mild</td>\n",
281
+ " <td>0</td>\n",
282
+ " </tr>\n",
283
+ " </tbody>\n",
284
+ "</table>\n",
285
+ "</div>"
286
+ ],
287
+ "text/plain": [
288
+ " person_id age gender occupation bmi country \\\n",
289
+ "0 1 29 Female Driver 25.7 Japan \n",
290
+ "1 2 55 Female Software Engineer 22.0 USA \n",
291
+ "2 3 42 Male Nurse 25.0 India \n",
292
+ "3 4 37 Female Student 29.5 India \n",
293
+ "4 5 23 Male Lawyer 23.6 Spain \n",
294
+ "\n",
295
+ " sleep_duration_hrs sleep_quality_score rem_percentage \\\n",
296
+ "0 6.19 6.6 22.5 \n",
297
+ "1 8.32 6.9 26.9 \n",
298
+ "2 3.74 1.0 20.2 \n",
299
+ "3 6.79 6.4 17.7 \n",
300
+ "4 5.02 3.2 23.3 \n",
301
+ "\n",
302
+ " deep_sleep_percentage sleep_latency_mins wake_episodes_per_night \\\n",
303
+ "0 19.3 16 3 \n",
304
+ "1 14.9 17 4 \n",
305
+ "2 16.2 26 4 \n",
306
+ "3 17.7 13 4 \n",
307
+ "4 18.3 30 5 \n",
308
+ "\n",
309
+ " caffeine_mg_before_bed alcohol_units_before_bed \\\n",
310
+ "0 0 0.0 \n",
311
+ "1 0 0.0 \n",
312
+ "2 0 2.0 \n",
313
+ "3 0 1.0 \n",
314
+ "4 40 0.0 \n",
315
+ "\n",
316
+ " screen_time_before_bed_mins exercise_day steps_that_day \\\n",
317
+ "0 32 0 6592 \n",
318
+ "1 33 1 10111 \n",
319
+ "2 89 1 9222 \n",
320
+ "3 52 1 9190 \n",
321
+ "4 72 0 4273 \n",
322
+ "\n",
323
+ " nap_duration_mins stress_score work_hours_that_day chronotype \\\n",
324
+ "0 0 4.4 10.7 Morning \n",
325
+ "1 8 4.0 3.0 Neutral \n",
326
+ "2 28 7.8 3.6 Neutral \n",
327
+ "3 40 4.9 6.7 Morning \n",
328
+ "4 0 7.4 10.4 Neutral \n",
329
+ "\n",
330
+ " mental_health_condition heart_rate_resting_bpm sleep_aid_used shift_work \\\n",
331
+ "0 Healthy 63 0 0 \n",
332
+ "1 Healthy 52 1 0 \n",
333
+ "2 Both 72 0 1 \n",
334
+ "3 Healthy 71 0 0 \n",
335
+ "4 Healthy 71 0 0 \n",
336
+ "\n",
337
+ " room_temperature_celsius weekend_sleep_diff_hrs season day_type \\\n",
338
+ "0 20.1 1.84 Autumn Weekday \n",
339
+ "1 18.0 0.13 Winter Weekend \n",
340
+ "2 17.9 1.67 Spring Weekend \n",
341
+ "3 19.1 2.37 Summer Weekend \n",
342
+ "4 19.7 1.26 Summer Weekday \n",
343
+ "\n",
344
+ " cognitive_performance_score sleep_disorder_risk felt_rested \n",
345
+ "0 73.4 Healthy 0 \n",
346
+ "1 99.4 Healthy 1 \n",
347
+ "2 2.5 Severe 0 \n",
348
+ "3 67.8 Healthy 0 \n",
349
+ "4 38.1 Mild 0 "
350
+ ]
351
+ },
352
+ "metadata": {},
353
+ "output_type": "display_data"
354
+ },
355
+ {
356
+ "name": "stdout",
357
+ "output_type": "stream",
358
+ "text": [
359
+ "sleep_disorder_risk\n",
360
+ "Healthy 54156\n",
361
+ "Mild 33479\n",
362
+ "Moderate 8299\n",
363
+ "Severe 4066\n",
364
+ "Name: count, dtype: int64\n"
365
+ ]
366
+ }
367
+ ],
368
+ "source": [
369
+ "data_path = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\dataset\\sleep_health_dataset.csv'\n",
370
+ "df = pd.read_csv(data_path)\n",
371
+ "\n",
372
+ "print('Dataset shape:', df.shape)\n",
373
+ "display(df.head())\n",
374
+ "\n",
375
+ "target_column = 'sleep_disorder_risk'\n",
376
+ "print(df[target_column].value_counts())"
377
+ ]
378
+ },
379
+ {
380
+ "cell_type": "markdown",
381
+ "id": "45fc0292",
382
+ "metadata": {},
383
+ "source": [
384
+ "## Step 3: Prepare Data"
385
+ ]
386
+ },
387
+ {
388
+ "cell_type": "code",
389
+ "execution_count": 22,
390
+ "id": "e716742b",
391
+ "metadata": {},
392
+ "outputs": [
393
+ {
394
+ "name": "stdout",
395
+ "output_type": "stream",
396
+ "text": [
397
+ "X_train shape: (80000, 67)\n",
398
+ "X_test shape: (20000, 67)\n",
399
+ "Classes: ['Healthy', 'Mild', 'Moderate', 'Severe']\n"
400
+ ]
401
+ }
402
+ ],
403
+ "source": [
404
+ "X = df.drop(columns=[target_column])\n",
405
+ "y_text = df[target_column].astype(str)\n",
406
+ "\n",
407
+ "label_encoder = LabelEncoder()\n",
408
+ "y = label_encoder.fit_transform(y_text)\n",
409
+ "class_names = list(label_encoder.classes_)\n",
410
+ "\n",
411
+ "X = pd.get_dummies(X, drop_first=False)\n",
412
+ "\n",
413
+ "X_train, X_test, y_train, y_test = train_test_split(\n",
414
+ " X,\n",
415
+ " y,\n",
416
+ " test_size=0.2,\n",
417
+ " random_state=21,\n",
418
+ " stratify=y\n",
419
+ ")\n",
420
+ "\n",
421
+ "print('X_train shape:', X_train.shape)\n",
422
+ "print('X_test shape:', X_test.shape)\n",
423
+ "print('Classes:', class_names)"
424
+ ]
425
+ },
426
+ {
427
+ "cell_type": "markdown",
428
+ "id": "c632cb6b",
429
+ "metadata": {},
430
+ "source": [
431
+ "## Step 4: Train XGBoost Model"
432
+ ]
433
+ },
434
+ {
435
+ "cell_type": "code",
436
+ "execution_count": 23,
437
+ "id": "4bab4630",
438
+ "metadata": {},
439
+ "outputs": [
440
+ {
441
+ "name": "stdout",
442
+ "output_type": "stream",
443
+ "text": [
444
+ "Model training completed\n"
445
+ ]
446
+ }
447
+ ],
448
+ "source": [
449
+ "model = XGBClassifier(\n",
450
+ " objective='multi:softprob',\n",
451
+ " eval_metric='mlogloss',\n",
452
+ " num_class=len(class_names),\n",
453
+ " n_estimators=500,\n",
454
+ " learning_rate=0.05,\n",
455
+ " max_depth=5,\n",
456
+ " subsample=0.9,\n",
457
+ " colsample_bytree=0.9,\n",
458
+ " random_state=21,\n",
459
+ " n_jobs=-1\n",
460
+ ")\n",
461
+ "\n",
462
+ "model.fit(X_train, y_train)\n",
463
+ "\n",
464
+ "y_pred = model.predict(X_test)\n",
465
+ "y_prob = model.predict_proba(X_test)\n",
466
+ "\n",
467
+ "print('Model training completed')"
468
+ ]
469
+ },
470
+ {
471
+ "cell_type": "markdown",
472
+ "id": "55c1bd7c",
473
+ "metadata": {},
474
+ "source": [
475
+ "## Step 5: Check Model Performance"
476
+ ]
477
+ },
478
+ {
479
+ "cell_type": "code",
480
+ "execution_count": 24,
481
+ "id": "edfa16c9",
482
+ "metadata": {},
483
+ "outputs": [
484
+ {
485
+ "name": "stdout",
486
+ "output_type": "stream",
487
+ "text": [
488
+ "Accuracy: 0.953\n",
489
+ "Weighted Precision: 0.9517\n",
490
+ "Weighted Recall: 0.953\n",
491
+ "Weighted F1 Score: 0.9521\n",
492
+ "Weighted ROC AUC (OvR): 0.997\n",
493
+ "Log Loss: 0.1405\n",
494
+ "\n",
495
+ "Classification Report:\n",
496
+ "\n",
497
+ " precision recall f1-score support\n",
498
+ "\n",
499
+ " Healthy 0.9980 0.9983 0.9982 10831\n",
500
+ " Mild 0.9373 0.9661 0.9515 6696\n",
501
+ " Moderate 0.7452 0.6976 0.7206 1660\n",
502
+ " Severe 0.8759 0.7638 0.8160 813\n",
503
+ "\n",
504
+ " accuracy 0.9530 20000\n",
505
+ " macro avg 0.8891 0.8565 0.8716 20000\n",
506
+ "weighted avg 0.9517 0.9530 0.9521 20000\n",
507
+ "\n"
508
+ ]
509
+ }
510
+ ],
511
+ "source": [
512
+ "accuracy = accuracy_score(y_test, y_pred)\n",
513
+ "precision = precision_score(y_test, y_pred, average='weighted', zero_division=0)\n",
514
+ "recall = recall_score(y_test, y_pred, average='weighted', zero_division=0)\n",
515
+ "f1 = f1_score(y_test, y_pred, average='weighted', zero_division=0)\n",
516
+ "roc_auc = roc_auc_score(y_test, y_prob, multi_class='ovr', average='weighted')\n",
517
+ "loss = log_loss(y_test, y_prob)\n",
518
+ "\n",
519
+ "print('Accuracy:', round(accuracy, 4))\n",
520
+ "print('Weighted Precision:', round(precision, 4))\n",
521
+ "print('Weighted Recall:', round(recall, 4))\n",
522
+ "print('Weighted F1 Score:', round(f1, 4))\n",
523
+ "print('Weighted ROC AUC (OvR):', round(roc_auc, 4))\n",
524
+ "print('Log Loss:', round(loss, 4))\n",
525
+ "\n",
526
+ "print('\\nClassification Report:\\n')\n",
527
+ "print(classification_report(y_test, y_pred, target_names=class_names, digits=4, zero_division=0))"
528
+ ]
529
+ },
530
+ {
531
+ "cell_type": "markdown",
532
+ "id": "eb87f90b",
533
+ "metadata": {},
534
+ "source": [
535
+ "## Step 6: Plot Confusion Matrix"
536
+ ]
537
+ },
538
+ {
539
+ "cell_type": "code",
540
+ "execution_count": 25,
541
+ "id": "b22a6a06",
542
+ "metadata": {},
543
+ "outputs": [
544
+ {
545
+ "data": {
546
+ "image/png": "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S9wZ3esAAAAWevTokdlC06AydGBp8+DBg3D7bbefPYe78cig09cjfyoA4blXrRVcL54Pb/DwvJji668nSWBgYJh9HTp0kI4dO4a7b+zYscMFl7bbvr6+4s747QUAALBQ69atpXnz5mH2RZTlVClSpJCbN2+aus6YMWPau9w14EyYMKG4M2o6AQAALOTj4yPx48cPszkKOnPmzGmCzd27d9v37dixQ/LmzSvRo7t3WOferQMAAIBdnDhxpFatWtK/f3/Zu3evrFq1ykwO36RJE3F30UJCQkKsbgQAAAAiRwcTadC5YsUKkxVt2bKlNGvWTNwdQScAAABcju51AAAAuBxBJwAAAFyOoBMAAAAuR9BpgfLly8uSJUvC7dd9euy/+vTTT82mdJzY3LlzIzwG95U9e3azXbhwIdyx7777zhz7+uuvw11T3de4cWOH59VjtsfBmuv1orZu3Woe7ypnz56VtWvXuuz83iw4ONhc9woVKkiePHmkbNmyMnToULl7967VTQMsQdDp4bZt2yYDBw60uhl4CbFixZLff/893H6dHiNaqMV6e/fubTZEjevlbnr16mWmXYHzjRo1yowuHjx4sCxfvtwEnBs3bpSuXbvycsMrEXR6OGbEiroKFy4cLojRDMmuXbskV65c9n0JEiQwG6LG9YL3WLp0qXTu3FlKlCghadOmNV91mps1a9bIlStXrG4e8MoRdLqpixcvSps2bSR//vymy13XZH3y5In9+MKFC6Vq1aqmy6ZYsWIyYMCAMMfVuXPn7JPFavecdtPZ/hB+9NFH5tza3bNs2TKz/8cffzTn0qW1bH777TdzH4LXV0+75P78888wXXF//PGHCW7ixYsXqZKJlStXSpUqVaRAgQIm4/3s/xG8+utlK6V58803JV++fFKnTh3TI2Gjj//444+lYMGC5trt27cv0u8Net6GDRtK+/btxd/f3/xO6/l69uxpAh59v9D3Dc2+Kv1/o23Wc9jKMv7tvQeRpxnuLVu2yNOnT+379Lr+/PPPkjhxYrNetmZB9X1XN82A3rp1y9xP36N79OgR5nyffPKJvVfjRf8f6Hv4uHHjJCAgwPyf1MdGVA4CuBJBpxvSN4cOHTpI0qRJzSdl7ZLRwHDixInmuP6R0Dcq/cOkXTYacC5atEhWr14d5jypUqWy15Ft2LDBvNnZApHcuXPLTz/9ZP7waffa33//bf5oPnz40LxJ2vz666/mPu7cPeipsmXLZtbYXbdunX2fXruKFStG6vHHjh2TLl26SKNGjWTx4sXmw4QulQZrr5cGBIMGDTJrLX///ffyxhtvyIcffiiXL182x/v16ycnTpyQOXPmyGeffSbTp0+P9HuD0szq66+/LgsWLDABxpAhQ+TkyZNmxRL9ndeAQwMXDXj0q74vtGjRwrxXROb8iDz90D979mwTFOp11Q/x+h6r10fLMcaMGSP79++XKVOmyKxZs8wHBM2MqurVq5uMqNaFKr1eelv3v8z/A/3/pPcZPXq0zJ8/3zxWr7vt/MAroSsS4dUqV65cSJ48eUIKFCgQZtN9emzTpk0hxYsXD3ny5In9MatXrw4pWrSo+X7fvn0hy5YtC3POBg0ahAQGBprve/ToYTa1ZcuWkGzZstnvp/vfeecd++07d+6Y47t37za3O3fuHNK7d2/z/f3790279Pnwauk10Ws3ZMiQkE8++cTsCwoKCvH39w+5du1ayPvvvx8yduzYcNdb9+kxNWzYsJAmTZrYz/no0aOQgIAA++NgzfWqVatWyOjRo8P9/o4aNcr8PubMmTNk27Zt9mNz5syx/w7/23vD4sWLQ7Jnzx7y4MED+3Hdd+TIEfvt48ePm/NduHDB3A7dtn87P17cDz/8YN5zc+TIYV73ggULhixatMi8v+bOnTvk8OHD9vvevn3b3E/32f7/rF+/3n4dSpQoEfL48eOX+n9QunRpcx8bPY+eI/Q+wNVivprQFs/q1KmTVK5cOcw+LTjXka7Hjx83XSzaLWKj3TP6CfnmzZumi8zX11fGjh1rsllHjhyR06dPm0+zkZEuXTr797ZawKCgIPP1rbfeMtkVrTvSrkE/Pz/zfLCGZp/1/4pmKTdv3myyaZqhiAz9f5QzZ077bc2shL4Na66XXhft9gxNyx90v2YktYs0R44c9mN58+YN89jnvTcofT59f7DRNZq1O10zXppBPXDggNkfUZf5v51fu4TxYt5++22z6eunPU6acdQMs74Pa5ZRu8FD09f71KlTpiRKs+T6d0Hf2/WrllvEiBHjhf8f3Lt3Ty5dumS67KNH/18Hp95fnwt4VQg6LaJvCBkyZAi3T+kfrMyZM8v48ePDPU6DxPXr15s/WvrHpFSpUuZ77WKPLH3TepatZrN06dLmj5HWmGlXkHatwzq2PyraLa6BQ6VKlV7o8c/W4mrgCWuvV+zYscPt09+50HV/ofn4+Ni//7f3hojO3717d9PVWrNmTVNqkTx5cnnnnXcifK7InB+Rc/jwYVM+Yau31oC9Ro0aJnDUhINtxoBvv/1W4saNG+HfgmrVqpl6XE0E6CA1rcl8mf8Htg8YX331lWTKlCnM/V977TUuKV4ZajrdkL4paIF3kiRJTGCqmw4K0sym1lbqIKK6deuagSH169eXLFmyyJkzZyIc7POitZj6B07/UGotmk7tofVDsE7MmDGlTJky5g+O1nNFtp5TZc2aNcwgFA1q9A8hrL1e+vu9Z8+eMPv0tu7XQEI/GIS+bgcPHoz0e8OztEZQ6zi/+OILk4HV3+3bt2+bYxG9X7zo+eGYBnpajxv6+tneYzUDqUGhJgA0Y2l7rePHj2/qM69fv27uq/W+tvPoY7Qe92WuU8KECU0ge/XqVfv9teZ/5MiRJrsOvCoEnW5Iu1LSpEkj3bp1M13n27dvlz59+kicOHHMm1SiRIlM5kKPHT161HyS1jcTLTR/lj5GabG6rQv932gXuw5MSpkypQlcYH2XrX7Q0D8aoUsj/k2DBg3MdZ8wYYLpVh0+fDijVd3gejVr1sx0sWoWTP/g61yO+mGgXr16JujQjKQONNJAVGec0FHJkX1veJYGOHpMu2Y1KNFeEtu8vbb3C82yaRerBjoven44poM1deaPdu3amQE8+vrv3r3bDCjS17527domaaClTHqdtVRKs9JaKqXTK9k+xGhWVAcI6awDtoDyZa6T/r/78ssvzQcivd6aPd25c6f5oAO8KgSdbkjfNDRQ0MyUBg4dO3Y02RN9k1C2UYvaRda8eXPziVm7zQ4dOhTuXFoXVLJkSVM3FNlVR3TqDp3iRbt2YD39A6PdaS+S5VSazdD/Rzo9i5Zi6AcT/X8Ea6+X/l5pbZ1mpbTWT2ej0JHl2mOhNHjQEeX6u60fKN9///1IvzdEFHRqNktLZbTXYtiwYdK2bVvTxW57v9DAR4PRVq1avfD58Xwa5OmHCP3goKVKOmOBZp/1Q4d+wNDrq1NZaRZaX28NMidPnhwmcNTrdv/+/TC9Ti9znVq2bGk+2PTt29e8H2imdNq0aXSv45WKpqOJXu1Twt3pm6IGqtot9yKZNQAAAEcYSAQ7/fyhGRHtitNMCwEnAABwFjKdCFePZuu6sXX3AQAA/FcEnQAAAHA5BhIBAADA5Qg6AQAA4HIEnQAAAHA5gk4AAAC4HEEnAAAAXI6gE4DTlC9f3qyCZdt0KUBdvm/GjBlOe47GjRvL119/bb7XFV10+ze67OCCBQte+jmXLFlifjYAwMtjcngATtWrVy/7Eqq6HOSWLVukd+/ekihRIrP8njPpeSNDlwLV9at1yUAAgDXIdAJwqgQJEpi1vXVLlSqV1K5d26wvrStdueK5dPs3rPYLANYj6ATgcjFjxpRYsWKZrvFBgwaZla/Kli0rd+/elYsXL0qbNm0kf/78pgs7MDBQnjx5Yn/sypUrpUqVKlKgQAEZOHBgmGPPdq//8MMPpjtfz9WwYUM5ePCgbN26VXr27Cnnz583Xf7nzp0zQei4ceMkICBAChcubJ7/woUL9vNcvnxZWrVqZZ5Tg+YzZ87wvwQA/iOCTgAuExwcbDKcGzduNIGmrT5y5MiRJriMFy+edOjQQZImTSpLly6VoUOHyrJly0xXuDp27Jh06dJFGjVqJIsXLzbd9Tt27IjwudavX2+625s2bSo//vij5MmTR1q3bi0FCxY0Xf4pU6aUDRs2mOzrnDlzzPOMHj1a5s+fb56/RYsWpr2qc+fO8vTpU1m4cKF88MEHMnPmTP6XAMB/RE0nAKfq16+fyWaqhw8fiq+vrwkE3377bRPEaYazUKFC5vjmzZtNhlH3R48eXTJnziw9evQwmcn27dubQFMzkc2aNTP379Onj6xZsybC59Xg8a233jIBqurevbvJrt6+fdt0wceIEcN0+aupU6eadhYrVszc1gyqZj01cE2XLp3s2rXLPE/q1Kkla9assn//flm+fDn/UwDgPyDoBOBUnTp1ksqVK5vvY8eObQI9Dfhs0qRJY//++PHjcuvWLfH397fv0wyjBqs3b940x3PmzGk/pkFk6NuhnTx50nSp2/j4+JgA9ln37t2TS5cuyUcffWQCXRt9zlOnTklQUJAZ9KQBp03evHkJOgHgPyLoBOBU2lWdIUMGh8c1ELXR7nLNbo4fPz7c/WwDhJ4dBKSBp6O60ciw1YR+9dVXkilTpjDHXnvtNZN9jexzAgAij5pOAJbRoE+715MkSWICVd10oM/YsWMlWrRopmt73759YbKghw8fjvBc+tjQxzS41IFJWgOq57JJmDChCYyvXr1qf06t89Q6U82WZsuWzXTJnz592v6YQ4cOuew1AABvQdAJwDJaR6nd7d26dZMjR47I9u3bTd1mnDhxTJe8zqup9ZQTJkyQEydOyPDhw8OMMg9NR8brACIdkKQBow5K0oylTlCv59NAUrvPNbuqNaJffvml/P7772bfZ599Jjt37jRZ1yxZspgpnnTwkQaxq1atMgOPAAD/DUEnAMtoYKkBpWYwNcDs2LGjlClTxgSBSrOQelwnd9eJ5TU7qccjUqRIETM4SKdC0kFLmp3UUfA6kKl48eLmXDVq1DD7W7ZsKfXq1ZO+ffua82ogO23aNNO9rr744gtJnDixqREdM2aMCWgBAP9NtBBmTQYAAICLkekEAACAyxF0AgAAwOUIOgEAAOByBJ0AAABwOYJOAAAAuBxBJwAAAFyOoBMAAAAuR9AJAAAAlyPoBAAAgMsRdAIAAMDlCDoBAADgcgSdAAAAEFf7PyDCKC5K8Hj+AAAAAElFTkSuQmCC",
547
+ "text/plain": [
548
+ "<Figure size 700x600 with 2 Axes>"
549
+ ]
550
+ },
551
+ "metadata": {},
552
+ "output_type": "display_data"
553
+ }
554
+ ],
555
+ "source": [
556
+ "cm = confusion_matrix(y_test, y_pred)\n",
557
+ "\n",
558
+ "plt.figure(figsize=(7, 6))\n",
559
+ "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names)\n",
560
+ "plt.xlabel('Predicted')\n",
561
+ "plt.ylabel('Actual')\n",
562
+ "plt.title('Confusion Matrix')\n",
563
+ "plt.tight_layout()\n",
564
+ "plt.show()"
565
+ ]
566
+ },
567
+ {
568
+ "cell_type": "markdown",
569
+ "id": "93ace7a7",
570
+ "metadata": {},
571
+ "source": [
572
+ "## Step 7: Save Model and Metrics"
573
+ ]
574
+ },
575
+ {
576
+ "cell_type": "code",
577
+ "execution_count": 26,
578
+ "id": "38fd3179",
579
+ "metadata": {},
580
+ "outputs": [
581
+ {
582
+ "name": "stdout",
583
+ "output_type": "stream",
584
+ "text": [
585
+ "Saved model: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance\\model\\model.joblib\n",
586
+ "Saved columns: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance\\model\\feature_columns.json\n",
587
+ "Saved labels: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance\\model\\label_classes.json\n",
588
+ "Saved target info: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance\\model\\target_info.json\n",
589
+ "Saved metrics: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance\\metrics\\final_metircs.csv\n",
590
+ "Saved predictions: d:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance\\metrics\\prediction.csv\n"
591
+ ]
592
+ }
593
+ ],
594
+ "source": [
595
+ "base_output = r'd:\\B.Tech\\Semester 04\\01 Machine Learning\\LabAssginment\\outputs\\Sleep_Health_And_Daily_Performance'\n",
596
+ "model_dir = os.path.join(base_output, 'model')\n",
597
+ "metrics_dir = os.path.join(base_output, 'metrics')\n",
598
+ "\n",
599
+ "os.makedirs(model_dir, exist_ok=True)\n",
600
+ "os.makedirs(metrics_dir, exist_ok=True)\n",
601
+ "\n",
602
+ "model_path = os.path.join(model_dir, 'model.joblib')\n",
603
+ "columns_path = os.path.join(model_dir, 'feature_columns.json')\n",
604
+ "labels_path = os.path.join(model_dir, 'label_classes.json')\n",
605
+ "info_path = os.path.join(model_dir, 'target_info.json')\n",
606
+ "metrics_path = os.path.join(metrics_dir, 'final_metircs.csv')\n",
607
+ "predictions_path = os.path.join(metrics_dir, 'prediction.csv')\n",
608
+ "\n",
609
+ "joblib.dump(model, model_path)\n",
610
+ "with open(columns_path, 'w', encoding='utf-8') as f:\n",
611
+ " json.dump(list(X.columns), f, indent=2)\n",
612
+ "with open(labels_path, 'w', encoding='utf-8') as f:\n",
613
+ " json.dump(class_names, f, indent=2)\n",
614
+ "with open(info_path, 'w', encoding='utf-8') as f:\n",
615
+ " json.dump({'target_col': target_column, 'model_name': 'XGBClassifier'}, f, indent=2)\n",
616
+ "\n",
617
+ "final_metrics = pd.DataFrame([\n",
618
+ " {'metric': 'accuracy', 'value': accuracy},\n",
619
+ " {'metric': 'precision_weighted', 'value': precision},\n",
620
+ " {'metric': 'recall_weighted', 'value': recall},\n",
621
+ " {'metric': 'f1_weighted', 'value': f1},\n",
622
+ " {'metric': 'roc_auc_ovr_weighted', 'value': roc_auc},\n",
623
+ " {'metric': 'log_loss', 'value': loss}\n",
624
+ "])\n",
625
+ "final_metrics.to_csv(metrics_path, index=False)\n",
626
+ "\n",
627
+ "actual_labels = [class_names[i] for i in y_test]\n",
628
+ "pred_labels = [class_names[i] for i in y_pred]\n",
629
+ "prediction_df = pd.DataFrame({'Actual': actual_labels, 'Predicted': pred_labels})\n",
630
+ "prediction_df.to_csv(predictions_path, index=False)\n",
631
+ "\n",
632
+ "print('Saved model:', model_path)\n",
633
+ "print('Saved columns:', columns_path)\n",
634
+ "print('Saved labels:', labels_path)\n",
635
+ "print('Saved target info:', info_path)\n",
636
+ "print('Saved metrics:', metrics_path)\n",
637
+ "print('Saved predictions:', predictions_path)"
638
+ ]
639
+ },
640
+ {
641
+ "cell_type": "markdown",
642
+ "id": "c64bc279",
643
+ "metadata": {},
644
+ "source": [
645
+ "## Step 8: Final Summary"
646
+ ]
647
+ },
648
+ {
649
+ "cell_type": "code",
650
+ "execution_count": 27,
651
+ "id": "fb9802ef",
652
+ "metadata": {},
653
+ "outputs": [
654
+ {
655
+ "data": {
656
+ "text/html": [
657
+ "<div>\n",
658
+ "<style scoped>\n",
659
+ " .dataframe tbody tr th:only-of-type {\n",
660
+ " vertical-align: middle;\n",
661
+ " }\n",
662
+ "\n",
663
+ " .dataframe tbody tr th {\n",
664
+ " vertical-align: top;\n",
665
+ " }\n",
666
+ "\n",
667
+ " .dataframe thead th {\n",
668
+ " text-align: right;\n",
669
+ " }\n",
670
+ "</style>\n",
671
+ "<table border=\"1\" class=\"dataframe\">\n",
672
+ " <thead>\n",
673
+ " <tr style=\"text-align: right;\">\n",
674
+ " <th></th>\n",
675
+ " <th>Metric</th>\n",
676
+ " <th>Value</th>\n",
677
+ " </tr>\n",
678
+ " </thead>\n",
679
+ " <tbody>\n",
680
+ " <tr>\n",
681
+ " <th>0</th>\n",
682
+ " <td>Accuracy</td>\n",
683
+ " <td>0.953050</td>\n",
684
+ " </tr>\n",
685
+ " <tr>\n",
686
+ " <th>1</th>\n",
687
+ " <td>Weighted Precision</td>\n",
688
+ " <td>0.951701</td>\n",
689
+ " </tr>\n",
690
+ " <tr>\n",
691
+ " <th>2</th>\n",
692
+ " <td>Weighted Recall</td>\n",
693
+ " <td>0.953050</td>\n",
694
+ " </tr>\n",
695
+ " <tr>\n",
696
+ " <th>3</th>\n",
697
+ " <td>Weighted F1 Score</td>\n",
698
+ " <td>0.952081</td>\n",
699
+ " </tr>\n",
700
+ " <tr>\n",
701
+ " <th>4</th>\n",
702
+ " <td>Weighted ROC AUC (OvR)</td>\n",
703
+ " <td>0.996961</td>\n",
704
+ " </tr>\n",
705
+ " <tr>\n",
706
+ " <th>5</th>\n",
707
+ " <td>Log Loss</td>\n",
708
+ " <td>0.140510</td>\n",
709
+ " </tr>\n",
710
+ " </tbody>\n",
711
+ "</table>\n",
712
+ "</div>"
713
+ ],
714
+ "text/plain": [
715
+ " Metric Value\n",
716
+ "0 Accuracy 0.953050\n",
717
+ "1 Weighted Precision 0.951701\n",
718
+ "2 Weighted Recall 0.953050\n",
719
+ "3 Weighted F1 Score 0.952081\n",
720
+ "4 Weighted ROC AUC (OvR) 0.996961\n",
721
+ "5 Log Loss 0.140510"
722
+ ]
723
+ },
724
+ "metadata": {},
725
+ "output_type": "display_data"
726
+ },
727
+ {
728
+ "name": "stdout",
729
+ "output_type": "stream",
730
+ "text": [
731
+ "Final model used: XGBClassifier\n",
732
+ "Target column: sleep_disorder_risk\n",
733
+ "Classes: ['Healthy', 'Mild', 'Moderate', 'Severe']\n"
734
+ ]
735
+ }
736
+ ],
737
+ "source": [
738
+ "summary = pd.DataFrame([\n",
739
+ " {'Metric': 'Accuracy', 'Value': accuracy},\n",
740
+ " {'Metric': 'Weighted Precision', 'Value': precision},\n",
741
+ " {'Metric': 'Weighted Recall', 'Value': recall},\n",
742
+ " {'Metric': 'Weighted F1 Score', 'Value': f1},\n",
743
+ " {'Metric': 'Weighted ROC AUC (OvR)', 'Value': roc_auc},\n",
744
+ " {'Metric': 'Log Loss', 'Value': loss}\n",
745
+ "])\n",
746
+ "\n",
747
+ "display(summary)\n",
748
+ "print('Final model used: XGBClassifier')\n",
749
+ "print('Target column:', target_column)\n",
750
+ "print('Classes:', class_names)"
751
+ ]
752
+ }
753
+ ],
754
+ "metadata": {
755
+ "kernelspec": {
756
+ "display_name": ".venv",
757
+ "language": "python",
758
+ "name": "python3"
759
+ },
760
+ "language_info": {
761
+ "codemirror_mode": {
762
+ "name": "ipython",
763
+ "version": 3
764
+ },
765
+ "file_extension": ".py",
766
+ "mimetype": "text/x-python",
767
+ "name": "python",
768
+ "nbconvert_exporter": "python",
769
+ "pygments_lexer": "ipython3",
770
+ "version": "3.11.9"
771
+ }
772
+ },
773
+ "nbformat": 4,
774
+ "nbformat_minor": 5
775
+ }
outputs/Consumer_Shopping_Trends/metrics/final_metircs.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3493db507aed6e2b48978aebcf36ef4f7c611d081b13904db0df7de25ce9b05b
3
+ size 105
outputs/Consumer_Shopping_Trends/metrics/prediction.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0aaa7342b3882a07cd880c4b0c8fc18e13810df8024ad5ccea91a348736c7139
3
+ size 31292
outputs/Consumer_Shopping_Trends/model/feature_columns.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "age",
3
+ "monthly_income",
4
+ "daily_internet_hours",
5
+ "smartphone_usage_years",
6
+ "social_media_hours",
7
+ "online_payment_trust_score",
8
+ "tech_savvy_score",
9
+ "monthly_online_orders",
10
+ "monthly_store_visits",
11
+ "avg_online_spend",
12
+ "avg_store_spend",
13
+ "discount_sensitivity",
14
+ "return_frequency",
15
+ "avg_delivery_days",
16
+ "delivery_fee_sensitivity",
17
+ "free_return_importance",
18
+ "product_availability_online",
19
+ "impulse_buying_score",
20
+ "need_touch_feel_score",
21
+ "brand_loyalty_score",
22
+ "environmental_awareness",
23
+ "time_pressure_level",
24
+ "gender_Female",
25
+ "gender_Male",
26
+ "gender_Other",
27
+ "city_tier_Tier 1",
28
+ "city_tier_Tier 2",
29
+ "city_tier_Tier 3"
30
+ ]
outputs/Consumer_Shopping_Trends/model/label_classes.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [
2
+ "Hybrid",
3
+ "Online",
4
+ "Store"
5
+ ]
outputs/Consumer_Shopping_Trends/model/model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3f1ae8dfac5dfe158add4bdf494ee361bc83456ab4d1cd8ccebfd3b4eebc3e35
3
+ size 2399
outputs/Consumer_Shopping_Trends/model/target_info.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "target_col": "shopping_preference",
3
+ "model_name": "LogisticRegression"
4
+ }
outputs/F1_Strategy/metrics/final_metircs.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:29f0ea2c7a7e224ff2f30aea14968bd457d4739c167d437b803273c2b44dfb20
3
+ size 181
outputs/F1_Strategy/metrics/prediction.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c0a88e3f44651b8f2a0fca248d2b4cf1f61684440aecd420bb1525bcfd5fcd62
3
+ size 375769
outputs/F1_Strategy/model/feature_columns.json ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "LapNumber",
3
+ "Stint",
4
+ "TyreLife",
5
+ "Position",
6
+ "LapTime (s)",
7
+ "Year",
8
+ "LapTime_Delta",
9
+ "Cumulative_Degradation",
10
+ "PitStop",
11
+ "RaceProgress",
12
+ "Normalized_TyreLife",
13
+ "Position_Change",
14
+ "Driver_ALB",
15
+ "Driver_ALO",
16
+ "Driver_ANT",
17
+ "Driver_BEA",
18
+ "Driver_BOR",
19
+ "Driver_BOT",
20
+ "Driver_COL",
21
+ "Driver_DEV",
22
+ "Driver_DOO",
23
+ "Driver_GAS",
24
+ "Driver_HAD",
25
+ "Driver_HAM",
26
+ "Driver_HUL",
27
+ "Driver_LAT",
28
+ "Driver_LAW",
29
+ "Driver_LEC",
30
+ "Driver_MAG",
31
+ "Driver_MSC",
32
+ "Driver_NOR",
33
+ "Driver_OCO",
34
+ "Driver_PER",
35
+ "Driver_PIA",
36
+ "Driver_RIC",
37
+ "Driver_RUS",
38
+ "Driver_SAI",
39
+ "Driver_SAR",
40
+ "Driver_STR",
41
+ "Driver_TSU",
42
+ "Driver_VER",
43
+ "Driver_VET",
44
+ "Driver_ZHO",
45
+ "Compound_HARD",
46
+ "Compound_INTERMEDIATE",
47
+ "Compound_MEDIUM",
48
+ "Compound_SOFT",
49
+ "Compound_WET",
50
+ "Race_Abu Dhabi Grand Prix",
51
+ "Race_Australian Grand Prix",
52
+ "Race_Austrian Grand Prix",
53
+ "Race_Azerbaijan Grand Prix",
54
+ "Race_Bahrain Grand Prix",
55
+ "Race_Belgian Grand Prix",
56
+ "Race_British Grand Prix",
57
+ "Race_Canadian Grand Prix",
58
+ "Race_Chinese Grand Prix",
59
+ "Race_Dutch Grand Prix",
60
+ "Race_Emilia Romagna Grand Prix",
61
+ "Race_French Grand Prix",
62
+ "Race_Hungarian Grand Prix",
63
+ "Race_Italian Grand Prix",
64
+ "Race_Japanese Grand Prix",
65
+ "Race_Las Vegas Grand Prix",
66
+ "Race_Mexico City Grand Prix",
67
+ "Race_Miami Grand Prix",
68
+ "Race_Monaco Grand Prix",
69
+ "Race_Pre-Season Test",
70
+ "Race_Pre-Season Testing",
71
+ "Race_Pre-Season Track Session",
72
+ "Race_Qatar Grand Prix",
73
+ "Race_Saudi Arabian Grand Prix",
74
+ "Race_Singapore Grand Prix",
75
+ "Race_Spanish Grand Prix",
76
+ "Race_S\u00e3o Paulo Grand Prix",
77
+ "Race_United States Grand Prix"
78
+ ]
outputs/F1_Strategy/model/label_classes.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ [
2
+ 0,
3
+ 1
4
+ ]
outputs/F1_Strategy/model/model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c88fcae16b2717808b50af7b4f56abbfc696de6091a56adf50778fd508c288e
3
+ size 199351129
outputs/F1_Strategy/model/target_info.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "target_col": "PitNextLap",
3
+ "model_name": "RandomForestClassifier"
4
+ }
outputs/Sleep_Health_And_Daily_Performance/metrics/final_metircs.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ed4f6ea761170cc001686ec6016e741bcf8dce4da7fb8360a83647a3f771f47
3
+ size 198
outputs/Sleep_Health_And_Daily_Performance/metrics/prediction.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b10d56959f144df9f33a0c65dfab6c1a1aac9109369ad5429df10d4c3466dd7b
3
+ size 300916
outputs/Sleep_Health_And_Daily_Performance/model/feature_columns.json ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "person_id",
3
+ "age",
4
+ "bmi",
5
+ "sleep_duration_hrs",
6
+ "sleep_quality_score",
7
+ "rem_percentage",
8
+ "deep_sleep_percentage",
9
+ "sleep_latency_mins",
10
+ "wake_episodes_per_night",
11
+ "caffeine_mg_before_bed",
12
+ "alcohol_units_before_bed",
13
+ "screen_time_before_bed_mins",
14
+ "exercise_day",
15
+ "steps_that_day",
16
+ "nap_duration_mins",
17
+ "stress_score",
18
+ "work_hours_that_day",
19
+ "heart_rate_resting_bpm",
20
+ "sleep_aid_used",
21
+ "shift_work",
22
+ "room_temperature_celsius",
23
+ "weekend_sleep_diff_hrs",
24
+ "cognitive_performance_score",
25
+ "felt_rested",
26
+ "gender_Female",
27
+ "gender_Male",
28
+ "gender_Other",
29
+ "occupation_Doctor",
30
+ "occupation_Driver",
31
+ "occupation_Freelancer",
32
+ "occupation_Homemaker",
33
+ "occupation_Lawyer",
34
+ "occupation_Manager",
35
+ "occupation_Nurse",
36
+ "occupation_Retired",
37
+ "occupation_Sales",
38
+ "occupation_Software Engineer",
39
+ "occupation_Student",
40
+ "occupation_Teacher",
41
+ "country_Australia",
42
+ "country_Brazil",
43
+ "country_Canada",
44
+ "country_France",
45
+ "country_Germany",
46
+ "country_India",
47
+ "country_Italy",
48
+ "country_Japan",
49
+ "country_Mexico",
50
+ "country_Netherlands",
51
+ "country_South Korea",
52
+ "country_Spain",
53
+ "country_Sweden",
54
+ "country_UK",
55
+ "country_USA",
56
+ "chronotype_Evening",
57
+ "chronotype_Morning",
58
+ "chronotype_Neutral",
59
+ "mental_health_condition_Anxiety",
60
+ "mental_health_condition_Both",
61
+ "mental_health_condition_Depression",
62
+ "mental_health_condition_Healthy",
63
+ "season_Autumn",
64
+ "season_Spring",
65
+ "season_Summer",
66
+ "season_Winter",
67
+ "day_type_Weekday",
68
+ "day_type_Weekend"
69
+ ]
outputs/Sleep_Health_And_Daily_Performance/model/label_classes.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ [
2
+ "Healthy",
3
+ "Mild",
4
+ "Moderate",
5
+ "Severe"
6
+ ]
outputs/Sleep_Health_And_Daily_Performance/model/model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2916ff9b330c54e63af09d56ffdb0a7c1856594a7ce7fb107f8c083a951b8a4a
3
+ size 5449716
outputs/Sleep_Health_And_Daily_Performance/model/target_info.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "target_col": "sleep_disorder_risk",
3
+ "model_name": "XGBClassifier"
4
+ }
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ numpy
2
+ pandas
3
+ seaborn
4
+ matplotlib
5
+ scikit-learn
6
+ xgboost
7
+ fastapi
8
+ uvicorn[standard]
9
+ jinja2
10
+ shap
11
+ joblib