Spaces:
Sleeping
Sleeping
| # Case Study β NephroScreen | |
| *An end-to-end applied machine-learning project: screening for chronic kidney | |
| disease from routine lab values, built and analysed solo.* | |
| --- | |
| ## At a glance | |
| | | | | |
| |---|---| | |
| | **Problem** | Binary classification β predict CKD vs. healthy from routine clinical/lab features | | |
| | **Data** | UCI Chronic Kidney Disease dataset β 400 patients, 24 attributes, ~11% cells missing | | |
| | **Best model** | Random Forest β 100% test accuracy, ROC-AUC 1.0000 (read the caveat below) | | |
| | **Stack** | scikit-learn Β· FastAPI Β· vanilla JS frontend Β· Docker/Render | | |
| | **What makes it mature** | Leakage-safe pipeline Β· SHAP interpretability Β· calibration Β· screening-tuned threshold Β· tests | | |
| ## 1. The problem | |
| Chronic kidney disease often progresses silently until it is advanced, yet the | |
| warning signs are already present in routine blood and urine tests. The task: | |
| given a patient's lab panel, flag likely CKD early enough to matter. This is a | |
| classic **screening** problem, which shapes every later decision β especially | |
| how errors are weighted. | |
| ## 2. The data β and an honest catch | |
| The UCI CKD dataset is small (400 rows) and the two classes are **highly | |
| separable** on a few biomarkers. Every model I trained scored in the high 90s, | |
| and Random Forest reached **100% on the held-out test set**. | |
| **That number is a red flag, not a trophy.** With an 80-patient test set drawn | |
| from one clean, single-source dataset, near-perfect accuracy reflects the | |
| *separability of this data*, not real-world clinical performance. I treat it that | |
| way throughout β the engineering value here is doing the workflow *correctly*, | |
| not chasing a headline metric. | |
| ## 3. Approach | |
| **Leakage-safe preprocessing.** All imputation, encoding, and feature | |
| engineering live in a custom scikit-learn transformer (`CKDPreprocessor`) wrapped | |
| in a `Pipeline`. Every statistic β medians, modes, the scaler, the `age_group` | |
| fill value β is fit on the **training fold only**, so cross-validation and the | |
| live API never see test-set information. | |
| **Domain feature engineering.** Four clinically motivated features, e.g. | |
| `kidney_stress_index = serum_creatinine Γ blood_urea / hemoglobin` (the CKD triad | |
| in one number). SHAP later confirmed it earns its place in the top five. | |
| **Three models compared** with 10-fold stratified CV: | |
| | Model | Accuracy | Recall | F1 | ROC-AUC | 10-Fold CV | | |
| |---|---|---|---|---|---| | |
| | Logistic Regression | 98.75% | 98% | 98.99% | 1.0000 | 99.38% Β± 1.25% | | |
| | **Random Forest** | **100%** | **100%** | **100%** | **1.0000** | 98.75% Β± 2.86% | | |
| | Decision Tree | 97.50% | 98% | 98.00% | 0.9893 | 96.56% Β± 2.19% | | |
| ## 4. Interpretability β what the model actually uses | |
|  | |
| SHAP attributions are clinically coherent and build trust in the model: | |
| - **Low hemoglobin and PCV** push strongly toward CKD (anemia of kidney disease). | |
| - **High serum creatinine** and the engineered **kidney_stress_index** are top drivers. | |
| - **Hypertension and diabetes = "yes"** push toward CKD β the two leading real-world causes. | |
| This is the difference between "the model is accurate" and "I understand *why* | |
| the model is accurate" β the latter is what makes a result defensible. | |
| ## 5. Maturity signals | |
| **Calibration.** Do the probabilities mean what they say? The reliability curve | |
| checks whether an "0.8" prediction is CKD ~80% of the time β essential before | |
| anyone acts on a probability. | |
|  | |
| **A screening-tuned threshold.** For screening, a *missed* CKD case (false | |
| negative) is far costlier than a false alarm. Instead of the default 0.5, the API | |
| serves a threshold tuned to keep **recall β₯ 0.99**, accepting more false | |
| positives as the safer trade. | |
|  | |
| ## 6. From model to product | |
| The trained pipeline is persisted with `joblib` and served by a **FastAPI** | |
| backend; a dependency-free web frontend collects a lab panel (missing fields are | |
| imputed) and shows the probability, a risk band, and which entered values fall | |
| outside normal clinical ranges. Containerised for free-tier deployment. | |
| ``` | |
| Browser β FastAPI /api/predict β joblib pipeline (CKDPreprocessor β RandomForest) | |
| ``` | |
| ## 7. Limitations & what I'd do next | |
| - **Single small dataset** β results will not transfer to real clinical | |
| populations without external validation on an independent cohort. | |
| - **No serving-time calibration** yet; probabilities should be calibrated | |
| (e.g. isotonic) before being shown as clinical likelihoods. | |
| - **Next experiment:** find a **minimal, low-cost feature subset** β how much | |
| accuracy is lost if the expensive labs are dropped? That's the question with | |
| real value for screening in resource-limited settings. | |
| --- | |
| *Built solo by Sadiq Mansoor. Dataset: UCI ML Repository β Chronic Kidney | |
| Disease (ID 336). Educational project, not a medical device.* | |