tabml-guard 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. tabml_guard-0.1.0/LICENSE +21 -0
  2. tabml_guard-0.1.0/PKG-INFO +510 -0
  3. tabml_guard-0.1.0/README.md +437 -0
  4. tabml_guard-0.1.0/pyproject.toml +93 -0
  5. tabml_guard-0.1.0/setup.cfg +4 -0
  6. tabml_guard-0.1.0/tabml_guard/__init__.py +12 -0
  7. tabml_guard-0.1.0/tabml_guard/__version__.py +2 -0
  8. tabml_guard-0.1.0/tabml_guard/baselines.py +416 -0
  9. tabml_guard-0.1.0/tabml_guard/boosting.py +260 -0
  10. tabml_guard-0.1.0/tabml_guard/cli.py +165 -0
  11. tabml_guard-0.1.0/tabml_guard/config.py +149 -0
  12. tabml_guard-0.1.0/tabml_guard/configs/appliances_energy.yaml +50 -0
  13. tabml_guard-0.1.0/tabml_guard/configs/bike_sharing.yaml +51 -0
  14. tabml_guard-0.1.0/tabml_guard/configs/default.yaml +37 -0
  15. tabml_guard-0.1.0/tabml_guard/configs/metro_traffic.yaml +49 -0
  16. tabml_guard-0.1.0/tabml_guard/configs/occupancy_detection.yaml +48 -0
  17. tabml_guard-0.1.0/tabml_guard/data.py +297 -0
  18. tabml_guard-0.1.0/tabml_guard/drift.py +452 -0
  19. tabml_guard-0.1.0/tabml_guard/explain.py +281 -0
  20. tabml_guard-0.1.0/tabml_guard/make_index.py +198 -0
  21. tabml_guard-0.1.0/tabml_guard/metrics.py +107 -0
  22. tabml_guard-0.1.0/tabml_guard/modeling.py +221 -0
  23. tabml_guard-0.1.0/tabml_guard/paths.py +80 -0
  24. tabml_guard-0.1.0/tabml_guard/pdf_report.py +1264 -0
  25. tabml_guard-0.1.0/tabml_guard/pipeline.py +228 -0
  26. tabml_guard-0.1.0/tabml_guard/plots.py +79 -0
  27. tabml_guard-0.1.0/tabml_guard/py.typed +1 -0
  28. tabml_guard-0.1.0/tabml_guard/report.py +1053 -0
  29. tabml_guard-0.1.0/tabml_guard/stats.py +155 -0
  30. tabml_guard-0.1.0/tabml_guard/tuning.py +440 -0
  31. tabml_guard-0.1.0/tabml_guard.egg-info/PKG-INFO +510 -0
  32. tabml_guard-0.1.0/tabml_guard.egg-info/SOURCES.txt +37 -0
  33. tabml_guard-0.1.0/tabml_guard.egg-info/dependency_links.txt +1 -0
  34. tabml_guard-0.1.0/tabml_guard.egg-info/entry_points.txt +2 -0
  35. tabml_guard-0.1.0/tabml_guard.egg-info/requires.txt +29 -0
  36. tabml_guard-0.1.0/tabml_guard.egg-info/top_level.txt +1 -0
  37. tabml_guard-0.1.0/tests/test_config.py +80 -0
  38. tabml_guard-0.1.0/tests/test_data.py +75 -0
  39. tabml_guard-0.1.0/tests/test_pipeline.py +71 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayush Gole and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,510 @@
1
+ Metadata-Version: 2.4
2
+ Name: tabml-guard
3
+ Version: 0.1.0
4
+ Summary: A configuration-driven, explainable, drift-aware AutoML pipeline for time-ordered tabular data.
5
+ Author-email: Ayush Gole <ayushgole1910@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Ayush Gole and contributors
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/Ayush-Gole8/tabml-guard
29
+ Project-URL: Repository, https://github.com/Ayush-Gole8/tabml-guard
30
+ Project-URL: Documentation, https://github.com/Ayush-Gole8/tabml-guard#readme
31
+ Project-URL: Bug Tracker, https://github.com/Ayush-Gole8/tabml-guard/issues
32
+ Keywords: automl,machine-learning,tabular,concept-drift,explainability,shap,hyperparameter-optimization,time-series
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
42
+ Classifier: Typing :: Typed
43
+ Requires-Python: >=3.10
44
+ Description-Content-Type: text/markdown
45
+ License-File: LICENSE
46
+ Requires-Dist: numpy>=1.24
47
+ Requires-Dist: pandas>=2.0
48
+ Requires-Dist: scipy>=1.10
49
+ Requires-Dist: pyyaml>=6.0
50
+ Requires-Dist: joblib>=1.3
51
+ Requires-Dist: scikit-learn>=1.3
52
+ Requires-Dist: xgboost>=2.0
53
+ Requires-Dist: lightgbm>=4.0
54
+ Requires-Dist: catboost>=1.2
55
+ Requires-Dist: optuna>=3.4
56
+ Requires-Dist: shap>=0.44
57
+ Requires-Dist: statsmodels>=0.14
58
+ Requires-Dist: matplotlib>=3.7
59
+ Requires-Dist: seaborn>=0.12
60
+ Requires-Dist: reportlab>=4.0
61
+ Provides-Extra: drift
62
+ Requires-Dist: river>=0.21; extra == "drift"
63
+ Provides-Extra: dev
64
+ Requires-Dist: pytest>=8.0; extra == "dev"
65
+ Requires-Dist: pytest-cov; extra == "dev"
66
+ Requires-Dist: ruff>=0.4; extra == "dev"
67
+ Requires-Dist: mypy>=1.8; extra == "dev"
68
+ Requires-Dist: build; extra == "dev"
69
+ Requires-Dist: twine; extra == "dev"
70
+ Provides-Extra: all
71
+ Requires-Dist: tabml-guard[dev,drift]; extra == "all"
72
+ Dynamic: license-file
73
+
74
+ # TabML-Guard
75
+
76
+ [![PyPI](https://img.shields.io/pypi/v/tabml-guard?color=blue)](https://pypi.org/project/tabml-guard/)
77
+ [![Python](https://img.shields.io/badge/Python-3.10--3.12-3776AB?logo=python&logoColor=white)](https://pypi.org/project/tabml-guard/)
78
+ [![License](https://img.shields.io/badge/License-MIT-0f766e)](LICENSE)
79
+ [![CI](https://github.com/Ayush-Gole8/tabml-guard/actions/workflows/ci.yml/badge.svg)](https://github.com/Ayush-Gole8/tabml-guard/actions)
80
+ [![Config-driven](https://img.shields.io/badge/config--driven-yes-14213d)](config.yaml)
81
+
82
+ **An explainable, drift-aware AutoML pipeline and diagnostic framework for real-world, time-ordered tabular data.**
83
+
84
+ Point TabML-Guard at any tabular dataset (an OpenML name/id, local CSV, or in-memory DataFrame), name the target column and an optional time column, and it runs the entire modern supervised-learning workflow end to end: auto-detects classification vs. regression, splits the data *by time*, fits regularized baselines, trains boosting ensembles and a stacking meta-model, compares four hyperparameter-optimization strategies on **accuracy *and* compute cost**, explains the winning model with SHAP, flags its least-reliable predictions, monitors concept drift over time, runs statistical significance tests, and writes self-contained HTML and PDF diagnostic reports with a plain-English verdict on top.
85
+
86
+ The **tool is the deliverable**; the datasets are just demonstrations. Nothing in `tabml_guard/` knows about any specific dataset — every dataset detail lives in the config or CLI arguments.
87
+
88
+ ![TabML-Guard example outputs](assets/sample_outputs.png)
89
+
90
+ ---
91
+
92
+ ## Table of contents
93
+
94
+ - [Why TabML-Guard](#why-tabml-guard)
95
+ - [Installation](#installation)
96
+ - [Quickstart](#quickstart)
97
+ - [The pipeline](#the-pipeline)
98
+ - [Architecture & layers of abstraction](#architecture--layers-of-abstraction)
99
+ - [Configuration reference](#configuration-reference)
100
+ - [Run it on your own data](#run-it-on-your-own-data)
101
+ - [Outputs](#outputs)
102
+ - [Honest evaluation: temporal split & drift](#honest-evaluation-temporal-split--drift)
103
+ - [Extending TabML-Guard](#extending-tabml-guard)
104
+ - [Project structure](#project-structure)
105
+ - [Engineering notes](#engineering-notes)
106
+ - [Documentation](#documentation)
107
+ - [Literature backing](#literature-backing)
108
+ - [Roadmap & limitations](#roadmap--limitations)
109
+ - [License](#license)
110
+
111
+ ---
112
+
113
+ ## Why TabML-Guard
114
+
115
+ Most tabular ML code is written once, for one dataset, and quietly bakes in assumptions that make its numbers optimistic - it shuffles time-ordered rows, tunes on the test set, or reports a single accuracy figure with no notion of whether the model will still work next month. TabML-Guard is the opposite: a **reusable system** that makes the honest, defensible version of that workflow the default.
116
+
117
+ - **One file to touch.** Every dataset-specific decision lives in `config.yaml`. The pipeline code is generic.
118
+ - **Leakage-safe by construction.** The preprocessor is re-fit inside every cross-validation fold, and the temporal test set is never seen during model selection or tuning.
119
+ - **Cost-aware tuning.** It doesn't just ask "does Bayesian optimization win?" - it reports the wall-clock and evaluation budget each strategy spent, so you can decide whether tuning is worth it.
120
+ - **Trust, not just scores.** SHAP explains *why*, a reliability layer flags *which predictions to double-check*, and drift monitoring answers *will this keep working?*
121
+ - **Graceful degradation.** Optional dependencies (CatBoost, Optuna, `river`, SHAP) are lazily imported; if one is missing, that step is skipped with a note instead of crashing the run.
122
+ - **A report you can hand to a non-expert.** The output is a single self-contained `.html` file - no server, no notebook, no external assets.
123
+
124
+ ---
125
+
126
+ ## Installation
127
+
128
+ Requires **Python 3.10–3.12**.
129
+
130
+ ### From PyPI
131
+ ```bash
132
+ pip install tabml-guard
133
+ ```
134
+
135
+ ### From Source (Development)
136
+ ```bash
137
+ git clone https://github.com/Ayush-Gole8/tabml-guard.git
138
+ cd tabml-guard
139
+ python -m venv .venv
140
+ # Activate:
141
+ .venv\Scripts\activate # Windows
142
+ source .venv/bin/activate # macOS / Linux
143
+
144
+ pip install -e ".[all]"
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Quickstart
150
+
151
+ ### 1. Command Line Interface (CLI)
152
+
153
+ Run TabML-Guard directly on any CSV file:
154
+ ```bash
155
+ tabml-guard run --data sales.csv --target revenue --time timestamp
156
+ ```
157
+
158
+ Or run using a declarative YAML configuration file (e.g. on the built-in OpenML electricity benchmark):
159
+ ```bash
160
+ tabml-guard run --config config.yaml
161
+ ```
162
+
163
+ Scaffold a fresh configuration template in your working directory:
164
+ ```bash
165
+ tabml-guard init
166
+ ```
167
+
168
+ ### 2. Python Programmatic API
169
+
170
+ Use TabML-Guard inside your Python scripts or Jupyter Notebooks:
171
+ ```python
172
+ import pandas as pd
173
+ from tabml_guard import TabMLGuard
174
+
175
+ df = pd.read_csv("my_data.csv")
176
+
177
+ # Initialize and fit
178
+ guard = TabMLGuard(
179
+ target="churn",
180
+ time_col="signup_date", # or None for standard cross-validation
181
+ task="auto", # auto-detects classification vs. regression
182
+ output_dir="audit_results",
183
+ )
184
+ results = guard.fit(df)
185
+
186
+ # Export interactive HTML and formal PDF reports
187
+ guard.save_report("churn_audit.html", fmt="html")
188
+ guard.save_report("churn_audit.pdf", fmt="pdf")
189
+
190
+ # Production-ready scikit-learn pipeline (preprocessor + champion model)
191
+ model = guard.get_best_model()
192
+ predictions = guard.predict(df.drop(columns=["churn", "signup_date"]))
193
+ ```
194
+
195
+ When execution completes, inspect the self-contained reports:
196
+ ```
197
+ results/electricity/report_electricity.html
198
+ results/electricity/report_electricity.pdf
199
+ ```
200
+
201
+ ---
202
+
203
+ ## The pipeline
204
+
205
+ TabML-Guard runs six stages (S0–S5) and then assembles the report. Each stage hands the next an in-memory context object carrying the fitted models and the running "best model so far."
206
+
207
+ ```mermaid
208
+ flowchart TD
209
+ CFG["config.yaml - the one file you edit"]:::io
210
+ CFG --> S0
211
+ S0["S0 · Data<br/>load · profile · auto-detect task · temporal split"]:::st
212
+ S1["S1 · Regularized baselines<br/>L1 / L2 / ElasticNet · regularization path"]:::st
213
+ S2["S2 · Ensembles<br/>XGBoost · LightGBM · CatBoost · RandomForest · stacking"]:::st
214
+ S3["S3 · HPO comparison<br/>Default · Grid · Random · Bayesian - accuracy AND cost"]:::st
215
+ S4["S4 · Explainability<br/>SHAP global + local · confidence reliability triage"]:::st
216
+ S5["S5 · Drift + significance<br/>prequential · PSI · McNemar / Wilcoxon"]:::st
217
+ RPT["report_&lt;dataset&gt;.html<br/>self-contained · plain-English verdict on top"]:::io
218
+ S0 --> S1 --> S2 --> S3 --> S4 --> S5 --> RPT
219
+ classDef io fill:#0f766e,stroke:#0b544e,color:#ffffff;
220
+ classDef st fill:#eef2f7,stroke:#14213d,color:#14213d;
221
+ ```
222
+
223
+ | Stage | Module | What it does |
224
+ |---|---|---|
225
+ | **S0 - Data** | `src/data.py` | Loads from OpenML or CSV, profiles the columns, auto-detects the task from the target, builds a leakage-safe preprocessor, and splits **by time** (earliest rows train, latest rows test). |
226
+ | **S1 - Baselines** | `src/baselines.py` | Fits regularized linear baselines (L1/L2/ElasticNet for classification; Ridge/Lasso/ElasticNet for regression) and traces a **regularization path** so the bias–variance behaviour is visible. |
227
+ | **S2 - Ensembles** | `src/boosting.py` | Trains XGBoost, LightGBM, CatBoost and RandomForest, plus a **stacking** meta-model, and picks the best booster to carry into tuning. |
228
+ | **S3 - HPO comparison** | `src/tuning.py` | Runs Default vs Grid vs Random vs **Bayesian (Optuna)** on the best booster at an **equal budget**, recording accuracy *and* wall-clock/evaluation count - an explicit "is tuning worth the cost?" answer. |
229
+ | **S4 - Explainability** | `src/explain.py` | Computes SHAP (model-type-aware: tree/linear/agnostic) for global and local attributions, then a **reliability layer** that routes the least-confident predictions to human review. |
230
+ | **S5 - Drift & significance** | `src/drift.py`, `src/stats.py` | Walk-forward (prequential) monitoring of a frozen vs. periodically-retrained model, PSI input-drift tracking, and **McNemar** (classification) / **Wilcoxon** (regression) tests on the key model comparisons. |
231
+ | **Report** | `src/report.py` | Assembles everything into one self-contained HTML file with a verdict panel on top. |
232
+
233
+ ---
234
+
235
+ ## Architecture & layers of abstraction
236
+
237
+ TabML-Guard is deliberately layered so that **the surface a user touches is tiny**, while the machinery underneath stays open for developers. From top to bottom, each layer only depends on the one below it:
238
+
239
+ ```mermaid
240
+ flowchart TB
241
+ subgraph U["① User surface - what you touch"]
242
+ CFG["config.yaml"]
243
+ REP["report_&lt;dataset&gt;.html"]
244
+ end
245
+ subgraph O["② Orchestration"]
246
+ RUN["run.py<br/>threads ctx (fitted models, in-memory)<br/>and report_ctx (JSON-safe summaries)"]
247
+ end
248
+ subgraph E["③ Stage engine - src/"]
249
+ ENG["data · baselines · boosting<br/>tuning · explain · drift"]
250
+ end
251
+ subgraph C["④ Shared core - src/"]
252
+ CORE["modeling (the single fit/evaluate path) · metrics<br/>plots · stats · config · report"]
253
+ end
254
+ CFG --> RUN --> ENG --> CORE --> REP
255
+ ```
256
+
257
+ **As a user**, you interact with exactly two things: the config you write and the report you read. Everything else is an implementation detail.
258
+
259
+ **As a developer**, the map below tells you where each concern lives and whether you'd ever need to touch it:
260
+
261
+ | Module | Layer | Responsibility | Touch it? |
262
+ |---|---|---|---|
263
+ | `config.yaml` | ① surface | Every dataset-specific decision | **Always** - this is the interface |
264
+ | `run.py` | ② orchestration | Runs the stages in order; owns `ctx` and `report_ctx` | Rarely |
265
+ | `src/config.py` | core | Loads/validates YAML into typed dataclasses | Only to add a config field |
266
+ | `src/data.py` | engine | Load, profile, task-detect, preprocess, temporal split | To support a new source |
267
+ | `src/modeling.py` | core | **The single `fit_evaluate` path** every model goes through | Rarely - it's the invariant |
268
+ | `src/metrics.py` | core | Task-aware metrics | To add a metric |
269
+ | `src/baselines.py` | engine (S1) | Regularized baselines + regularization path | To change baselines |
270
+ | `src/boosting.py` | engine (S2) | Boosters + stacking | To add a booster |
271
+ | `src/tuning.py` | engine (S3) | The four HPO strategies + cost accounting | To add a strategy |
272
+ | `src/explain.py` | engine (S4) | SHAP + reliability triage | To change explanations |
273
+ | `src/drift.py` | engine (S5) | Prequential drift + PSI + retraining | To change drift logic |
274
+ | `src/stats.py` | core | McNemar / Wilcoxon significance tests | Rarely |
275
+ | `src/plots.py` | core | matplotlib figure helpers (headless) | To restyle figures |
276
+ | `src/report.py` | core | Pure-stdlib self-contained HTML assembly | To restyle the report |
277
+
278
+ The two objects `run.py` threads through the stages are worth calling out, because they encode a clean separation:
279
+
280
+ - **`ctx`** - an in-memory scratchpad holding *fitted estimators, predictions, and the running best model*. It is never serialized.
281
+ - **`report_ctx`** - only *JSON-safe per-stage summaries*. This is what gets written to `results/tables/results.json` and handed to the report builder.
282
+
283
+ ---
284
+
285
+ ## Configuration reference
286
+
287
+ The config is a single YAML file parsed into typed dataclasses (`src/config.py`), validated up front with actionable error messages. Every key under `dataset:` maps one-to-one to a loader field, so a typo raises immediately rather than being silently ignored.
288
+
289
+ ### `dataset`
290
+
291
+ | Key | Type | Default | Meaning |
292
+ |---|---|---|---|
293
+ | `source` | `openml` \| `csv` | `openml` | Where the data comes from. |
294
+ | `name` | string | `null` | OpenML dataset name (e.g. `electricity`). Uses `version`. |
295
+ | `data_id` | int | `null` | OpenML numeric id (canonical). **Overrides `name`** if both are set. |
296
+ | `path` | string | `null` | CSV path relative to the project root (required when `source: csv`). |
297
+ | `version` | int | `1` | OpenML version (ignored when `data_id` is set). |
298
+ | `target` | string | - | **Required.** The column to predict. |
299
+ | `time_col` | string | `null` | Column giving chronological order. `null` → random split + drift skipped. |
300
+ | `use_time_as_feature` | bool | `false` | Keep the time column as a model input (normally used only for ordering). |
301
+ | `drop_cols` | list | `[]` | Columns to exclude: IDs, free text, and above all **leakage**. |
302
+
303
+ ### Other sections
304
+
305
+ | Section · key | Default | Meaning |
306
+ |---|---|---|
307
+ | `task` | `auto` | `auto` \| `classification` \| `regression`. `auto` detects from the target. |
308
+ | `split.train_frac` / `split.test_frac` | `0.6` / `0.4` | Temporal split fractions (earliest → train, latest → test; no shuffle). |
309
+ | `models.boosters` | `[xgboost, lightgbm, catboost, random_forest]` | Which ensembles to train. |
310
+ | `models.stacking` | `true` | Add a stacking meta-model. |
311
+ | `tuning.budget_trials` | `40` | Optuna trials; Random search matched to the same budget. |
312
+ | `tuning.cv_splits` | `3` | Number of `TimeSeriesSplit` folds during tuning. |
313
+ | `tuning.scoring` | `null` | `null` → auto (ROC-AUC for classification, negative RMSE for regression). |
314
+ | `drift.n_chunks` | `10` | How many time-ordered chunks the test stream is cut into. |
315
+ | `drift.retrain` | `sliding` | `none` \| `periodic` \| `sliding` - how the adaptive model updates. |
316
+ | `report.title` | `"TabML-Guard Report"` | Title shown at the top of the report. |
317
+ | `report.fbeta` | `2.0` | β for the F-β score (2.0 weights recall higher). |
318
+ | `random_state` | `42` | Global seed for reproducibility. |
319
+ | `output_dir` | `results` | Where figures, tables, the model and the report are written. |
320
+
321
+ ---
322
+
323
+ ## Run it on your own data
324
+
325
+ Copy the closest shipped config, change `path` / `target` / `time_col` (and `drop_cols` if there's any leakage), and run. Nothing else changes:
326
+
327
+ ```yaml
328
+ dataset:
329
+ source: csv
330
+ path: data/my_data.csv
331
+ target: my_target_column
332
+ time_col: my_timestamp_column # or null if the data has no time order
333
+ drop_cols: [row_id] # exclude IDs / leakage
334
+ task: auto
335
+ ```
336
+
337
+ ```bash
338
+ python run.py --config configs/my_data.yaml
339
+ ```
340
+
341
+ The repo ships **five ready-to-run configs** that between them exercise every branch of the tool - both task types, the significance-test swap, the classification-only reliability layer, categorical one-hot encoding, and leakage removal:
342
+
343
+ | Config | Task | Demonstrates |
344
+ |---|---|---|
345
+ | `config.yaml` (electricity) | Classification | The baseline case; runs from OpenML with no download. |
346
+ | `configs/occupancy_detection.yaml` | Classification | A clean, all-numeric second classification story. |
347
+ | `configs/metro_traffic.yaml` | Regression | Flips the whole pipeline to RMSE/MAE/R² + Wilcoxon. |
348
+ | `configs/appliances_energy.yaml` | Regression | Dense numeric data - best case for PSI drift + the regularization path. |
349
+ | `configs/bike_sharing.yaml` | Regression | The `drop_cols` leakage demo. |
350
+
351
+ > See **[DATASETS.md](DATASETS.md)** for the full guide: where to download each CSV, what insight each run should surface, and the two built-in safety nets (a wrong column name prints the available columns; a high-cardinality categorical warns before it blows up memory).
352
+
353
+ ---
354
+
355
+ ## Outputs
356
+
357
+ Every run writes into `output_dir` (default `results/`):
358
+
359
+ ```
360
+ results/
361
+ ├── report_<dataset>.html # ← the headline deliverable: open this
362
+ ├── best_model.pkl # the best fitted pipeline (joblib) - ready to reuse
363
+ ├── tables/
364
+ │ ├── profile.json # dataset profile (shape, dtypes, target stats, time span)
365
+ │ └── results.json # JSON-safe per-stage summaries (the report's data)
366
+ └── figures/
367
+ ├── shap_importance.png
368
+ ├── reliability.png # classification runs only
369
+ ├── drift_performance.png
370
+ └── drift_psi.png
371
+ ```
372
+
373
+ The report embeds every figure as base64, so a single `.html` file is fully portable - you can email it or drop it on a static host and it just works. The figures above (shown in the banner) are the actual PNGs a run produces.
374
+
375
+ ---
376
+
377
+ ## Honest evaluation: temporal split & drift
378
+
379
+ The feature that most separates TabML-Guard from a typical notebook is that it never pretends the future is available at training time.
380
+
381
+ ```mermaid
382
+ flowchart LR
383
+ TR["Train - earliest 60%"]:::tr --> TE["Test stream - latest 40%"]:::te
384
+ TE --> K1["chunk 1"]:::ck --> K2["chunk 2"]:::ck --> K3["…"]:::ck --> KN["chunk n"]:::ck
385
+ classDef tr fill:#14213d,color:#fff,stroke:#0b1220;
386
+ classDef te fill:#0f766e,color:#fff,stroke:#0b544e;
387
+ classDef ck fill:#eef2f7,color:#14213d,stroke:#14213d;
388
+ ```
389
+
390
+ When a `time_col` is set, rows are sorted chronologically and split by time - the model trains on the past and is tested on the future. The test stream is then cut into `n_chunks` ordered slices, and two models are compared across them:
391
+
392
+ - a **frozen** model (trained once, never updated), and
393
+ - an **adaptive** model that, for each chunk, *predicts first and only then retrains* on it (`none` / `periodic` / `sliding` window) - so there is never any look-ahead.
394
+
395
+ Alongside performance, a **Population Stability Index (PSI)** trajectory tracks whether the *inputs themselves* are shifting, and McNemar/Wilcoxon tests confirm whether the differences are statistically real rather than noise. The gap between the frozen and adaptive curves is a direct, visual answer to "will this model keep working, and do I need to retrain it?"
396
+
397
+ ![Static vs adaptive performance over time](assets/drift_performance.png)
398
+
399
+ ---
400
+
401
+ ## Extending TabML-Guard
402
+
403
+ Because every stage funnels through one shared `fit_evaluate` path and a typed config, extensions are localized:
404
+
405
+ - **Add a model / booster** → add a `_build_*` constructor in `tabml_guard/boosting.py`, register it, and (optionally) add its search space to `tabml_guard/tuning.py` so it can be tuned.
406
+ - **Add a metric** → extend `classification_metrics` / `regression_metrics` in `tabml_guard/metrics.py`; every stage picks it up automatically.
407
+ - **Add a tuning strategy** → add a `_run_*` function in `tabml_guard/tuning.py` following the existing `refit=False` → pick-from-CV → single refit contract, so its cost is measured the same way.
408
+ - **Add a drift detector** → extend `_detect_changes` in `tabml_guard/drift.py` (the `river` ADWIN hook shows the pattern; it degrades gracefully when the library is absent).
409
+ - **Restyle the report** → it's pure-stdlib string assembly in `tabml_guard/report.py`; no template engine to learn.
410
+
411
+ ---
412
+
413
+ ## Project structure
414
+
415
+ ```
416
+ tabml-guard/
417
+ ├── pyproject.toml # PEP 517/518 build specification & dependencies
418
+ ├── CHANGELOG.md # version history & release notes
419
+ ├── LICENSE # MIT license
420
+ ├── config.yaml # reference configuration (electricity benchmark)
421
+ ├── configs/ # ready-to-run configurations for multiple tabular regimes
422
+ ├── tabml_guard/ # THE official installable Python package
423
+ │ ├── __init__.py # public API (TabMLGuard, load_config, Config)
424
+ │ ├── __version__.py # single source of version truth (0.1.0)
425
+ │ ├── py.typed # PEP 561 typing marker for IDEs & mypy
426
+ │ ├── cli.py # CLI entry point (tabml-guard run/init/index/version)
427
+ │ ├── pipeline.py # TabMLGuard class (high-level Python API)
428
+ │ ├── config.py # load + validate config into typed dataclasses
429
+ │ ├── data.py # S0: load, profile, task-detect, preprocess, temporal split
430
+ │ ├── modeling.py # shared fit/evaluate core (single source of truth)
431
+ │ ├── metrics.py # task-aware metrics
432
+ │ ├── baselines.py # S1: regularized baselines + regularization path
433
+ │ ├── boosting.py # S2: boosting ensembles + stacking
434
+ │ ├── tuning.py # S3: Default / Grid / Random / Bayesian + cost accounting
435
+ │ ├── explain.py # S4: SHAP + reliability triage
436
+ │ ├── drift.py # S5: prequential drift + PSI + retraining
437
+ │ ├── stats.py # McNemar's / Wilcoxon significance tests
438
+ │ ├── plots.py # headless matplotlib figure helpers
439
+ │ ├── paths.py # isolated output paths per dataset
440
+ │ ├── report.py # self-contained HTML report assembly
441
+ │ ├── pdf_report.py # publication-quality PDF report generation
442
+ │ └── configs/ # packaged configuration templates
443
+ ├── tests/ # automated unit & smoke test suite
444
+ │ ├── conftest.py # synthetic dataset fixtures
445
+ │ ├── test_config.py # configuration validation tests
446
+ │ ├── test_data.py # task detection & temporal split tests
447
+ │ └── test_pipeline.py # end-to-end pipeline execution test
448
+ ├── .github/
449
+ │ └── workflows/
450
+ │ ├── ci.yml # GitHub Actions linting & matrix testing
451
+ │ └── publish.yml # PyPI automated release workflow
452
+ ├── run.py # backwards-compatible entry point
453
+ ├── make_index.py # backwards-compatible results indexer
454
+ └── results/ # generated benchmark reports, models, and figures
455
+ ```
456
+
457
+ ---
458
+
459
+ ## Engineering notes
460
+
461
+ A few decisions that keep the numbers honest and the code robust:
462
+
463
+ - **One evaluation path.** Every model - baseline, booster, stacked, or tuned - is scored through the same `fit_evaluate` in `src/modeling.py`, so leaderboard rows are always comparable.
464
+ - **Leakage safety in three places.** The preprocessor is cloned and re-fit inside each pipeline (never fit on the whole dataset); the temporal test set is untouched during tuning and the regularization path (which uses an internal split of the training data only); and the adaptive drift model refits its preprocessor on each window.
465
+ - **Cross-validation correctness.** Stacking uses a `StratifiedKFold`/`KFold` partition (scikit-learn's `cross_val_predict` requires one), while Grid/Random tuning can safely use `TimeSeriesSplit` (they use `cross_validate`). This subtlety is handled for you.
466
+ - **Single-threaded base learners under parallel search.** Base learners are forced to one thread while the search parallelizes, avoiding cores² oversubscription.
467
+ - **Never crashes on a missing optional.** SHAP, CatBoost, Optuna and `river` are all lazily imported; a missing one skips its step with a recorded note.
468
+
469
+ ---
470
+
471
+ ## Documentation
472
+
473
+ | Document | What's in it |
474
+ |---|---|
475
+ | **[DATASETS.md](DATASETS.md)** | How to run on other datasets, where to get each CSV, and the insight each run should show. |
476
+ | **[PROJECT_PLAN.md](PROJECT_PLAN.md)** | Full design rationale, per-stage technique choices, and the mapping to the source papers. |
477
+ | **[EVALUATION_PREP.md](EVALUATION_PREP.md)** | An end-to-end walkthrough of the whole system, plain-English first then technical in depth. |
478
+
479
+ ---
480
+
481
+ ## Literature backing
482
+
483
+ The design synthesizes five peer-reviewed papers (all from Q1 journals) into one pipeline; each stage traces back to one of them:
484
+
485
+ | Paper | Journal | Informs |
486
+ |---|---|---|
487
+ | RegStack | *Expert Systems with Applications* (2025) | Regularized baselines + stacking (S1, S2) |
488
+ | Bayesian-Optimized XGBoost | *Expert Systems with Applications* (2026) | The tuning + SHAP backbone (S2–S4) |
489
+ | SHAP Error-Compensation | *IEEE Transactions on Artificial Intelligence* (2025) | Reliability-aware explainability (S4) |
490
+ | Concept Drift with Continuous Kernel Learning | *Information Sciences* (2024) | Drift monitoring; motivates the ELEC2 dataset (S5) |
491
+ | Spectral-Clustering Parameter Tuning | *IEEE Access* (2025) | The "parameters matter as much as algorithms" theme (lit-review) |
492
+
493
+ See `PROJECT_PLAN.md` for the full stage-by-stage mapping and DOIs.
494
+
495
+ ---
496
+
497
+ ## Roadmap & limitations
498
+
499
+ - **Compute-first honesty.** The point of the HPO stage is to *measure* the cost/benefit of tuning, not to guarantee tuning always wins - on easy datasets a regularized baseline can match tuned boosters, and the report says so.
500
+ - **Monocular drift signals.** PSI and prequential error are strong, cheap drift indicators but do not attribute *why* a distribution moved; deeper causal drift analysis is out of scope.
501
+ - **Single-machine scope.** The pipeline targets datasets that fit comfortably in memory on one machine (tens of thousands to low millions of rows); it is not a distributed training system.
502
+ - **Possible next steps.** A small pytest suite + CI badge, a CLI flag to select a subset of stages, and optional export of the report to PDF.
503
+
504
+ ---
505
+
506
+ ## License
507
+
508
+ Released under the **MIT License** - see [LICENSE](LICENSE).
509
+
510
+ > This began as an Advanced Machine Learning mini-project and is maintained as a reusable tool. If you use it, a link back is appreciated.