File size: 6,081 Bytes
11e8ba2
 
 
425532a
11e8ba2
 
 
 
 
 
 
dc3d345
 
bf27edd
 
 
 
dc3d345
bf27edd
 
 
dc3d345
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf27edd
 
 
 
 
 
 
 
 
 
 
 
 
 
dc3d345
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf27edd
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
---
title: NephroScreen
emoji: 🩺
colorFrom: green
colorTo: blue
sdk: docker
app_port: 8000
pinned: false
license: mit
---

# NephroScreen — Early Chronic Kidney Disease Screening

**An end-to-end applied machine-learning case study.** It estimates chronic
kidney disease (CKD) risk from routine clinical lab values — a **leakage-safe
scikit-learn pipeline**, a **FastAPI** service, and a **clean web interface**,
deployable on a free tier.

📄 **[Read the case study →](CASE_STUDY.md)** · ⚕️ *Educational project — not a
medical device.* Trained on a small public dataset (400 patients) and *not*
validated for clinical use.

---

## Why this project is interesting (the honest version)

On the UCI CKD dataset the models reach near-perfect scores — Random Forest hits
**100% test accuracy**. On most portfolios that number would be presented as the
headline. Here it is treated as a **teaching moment**, which is the more
defensible engineering story:

- The dataset is small (400 rows) and the classes are **highly separable** on a
  handful of biomarkers (hemoglobin, PCV, serum creatinine). 100% is a *property
  of the data*, not evidence of a deployable clinical model.
- The interesting engineering is therefore **not** the accuracy — it's doing the
  workflow *correctly*: no data leakage, honest validation, a screening-aware
  decision threshold, interpretability, and a reproducible serving path.

## Highlights

| Area | What's here |
|---|---|
| **Leakage-safe pipeline** | Custom `CKDPreprocessor` (imputation, encoding, feature engineering) fit on the **training fold only**, wrapped in a scikit-learn `Pipeline` so CV and serving never see test statistics. |
| **Screening-aware threshold** | Decision threshold tuned to keep **recall ≥ 0.99** — a missed CKD case is the costly error in screening. |
| **Interpretability** | Per-prediction, rule-based clinical indicators in the API; global + local **SHAP** analysis in the notebook. |
| **Serving** | Trained pipeline persisted with `joblib`; **FastAPI** loads it once and serves JSON predictions. |
| **Frontend** | Dependency-free HTML/CSS/JS single page; imputes missing fields so a partial lab panel still works. |
| **Reproducible** | Pinned dependencies; fixed seeds; automated `pytest` suite. |

## Model performance

Held-out test set (80 patients), 10-fold stratified CV, `random_state=42`:

| Model | Accuracy | Precision | Recall | F1 | ROC-AUC | 10-Fold CV |
|---|---|---|---|---|---|---|
| Logistic Regression | 98.75% | 100% | 98% | 98.99% | 1.0000 | 99.38% ± 1.25% |
| **Random Forest** (served) | **100%** | 100% | 100% | 100% | 1.0000 | 98.75% ± 2.86% |
| Decision Tree | 97.50% | 98% | 98% | 98% | 0.9893 | 96.56% ± 2.19% |

Top Random Forest predictors: hemoglobin, packed cell volume, serum creatinine,
`kidney_stress_index` (engineered), red blood cell count.

## Interpretability & maturity

Full write-up in the **[case study](CASE_STUDY.md)**. In short:

| SHAP — why the model decides | Calibration | Screening threshold |
|---|---|---|
| ![SHAP](assets/shap_summary.png) | ![Calibration](assets/calibration.png) | ![Threshold](assets/threshold.png) |

- **SHAP** attributions are clinically coherent (low hemoglobin/PCV, high
  creatinine, hypertension/diabetes drive CKD predictions).
- **Calibration** curve checks the probabilities are trustworthy, not just the labels.
- **Recall-tuned threshold (0.62)** — for screening, a missed case costs more
  than a false alarm, so the served threshold keeps recall ≥ 0.99.

## Architecture

```
Browser (frontend/)  ──POST /api/predict──►  FastAPI (api/)  ──►  joblib pipeline (models/)

                                     CKDPreprocessor → RandomForest
```

```
src/nephroscreen/   config · preprocessing · train        (the ML package)
api/                main (FastAPI) · schemas (pydantic)    (the service)
frontend/           index.html · style.css · app.js        (the UI)
models/             ckd_pipeline.joblib · metrics.json      (trained artifacts)
notebooks/          analysis.ipynb  (SHAP + calibration)    (the analysis)
tests/              test_api.py
```

## Quickstart

```bash
python -m venv .venv && source .venv/Scripts/activate   # Windows Git Bash
pip install -r requirements.txt && pip install -e .

python -m nephroscreen.train        # trains + saves models/ckd_pipeline.joblib
uvicorn api.main:app --reload       # http://127.0.0.1:8000
pytest                              # run the test suite
```

Open <http://127.0.0.1:8000> for the UI, or <http://127.0.0.1:8000/docs> for the
interactive API docs.

### API

```bash
curl -X POST http://127.0.0.1:8000/api/predict \
  -H "Content-Type: application/json" \
  -d '{"hemo": 9.5, "sc": 3.2, "al": 3, "htn": "yes", "dm": "yes"}'
```

```json
{ "prediction": "CKD", "probability": 0.98, "risk_band": "High",
  "threshold": 0.62, "key_indicators": [ ... ] }
```

## Deployment (free tier)

- **One box (recommended):** deploy the whole app on **Render** (`render.yaml`
  included) or any Docker host (`Dockerfile` included). FastAPI serves both the
  API and the static frontend.
- **Split hosting:** put `frontend/` on **Vercel/Netlify** (static) and the API
  on **Render**; set `window.NEPHRO_API_BASE` in the frontend to the API URL.
- Free instances sleep when idle; expect a ~30–50s cold start on first request.

## Limitations & future work

- Single small, single-source dataset → near-perfect scores do **not** transfer
  to real clinical populations.
- Future: external validation on an independent cohort; a **minimal low-cost
  feature subset** for resource-limited screening; fairness analysis across age
  and sex; hyperparameter tuning; probability calibration in the serving path.

## Credits

Solo project by **Sadiq Mansoor** — see [CREDITS.md](CREDITS.md). An independent
rebuild and extension of an earlier university coursework analysis. Dataset: UCI
ML Repository, Chronic Kidney Disease (ID 336). Licensed under [MIT](LICENSE).