4e815df327
400 rows / 200 patients, two samples each, so grouped splitting is actually exercised. Signal is modest and noisy on purpose — a separable toy would score 1.0 with a broken model and prove nothing.
52 lines
2.2 KiB
Python
52 lines
2.2 KiB
Python
"""Generate the smoke dataset. Deterministic — rerunning reproduces the file byte
|
|
for byte, so the committed CSV can always be regenerated and diffed.
|
|
|
|
Signal is real but modest: the label depends on age/iop/cdr with noise, so a
|
|
working pipeline lands around 0.85-0.95 AUC and a broken one sits near 0.5. A
|
|
perfectly separable toy would hide bugs by scoring 1.0 no matter what.
|
|
"""
|
|
import csv, math, random
|
|
from pathlib import Path
|
|
|
|
rng = random.Random(20260813)
|
|
N_PATIENTS, EYES = 200, 2
|
|
SITES = ["skovde", "gothenburg", "linkoping"]
|
|
out = Path(__file__).resolve().parents[1] / "data" / "smoke" / "labels.csv"
|
|
|
|
rows = []
|
|
for p in range(N_PATIENTS):
|
|
# Patient-level traits: both samples from one patient share them, which is
|
|
# what makes grouped splitting matter — a per-row split would leak.
|
|
base_age = rng.gauss(62, 11)
|
|
site = rng.choice(SITES)
|
|
frailty = rng.gauss(0, 1)
|
|
for e in range(EYES):
|
|
age = base_age + rng.gauss(0, 1.5)
|
|
iop = rng.gauss(16 + 2.2 * frailty, 2.8) # intraocular pressure
|
|
cdr = min(0.95, max(0.05, rng.gauss(0.45 + 0.09 * frailty, 0.11))) # cup/disc
|
|
rnfl = rng.gauss(95 - 7 * frailty, 9) # retinal nerve fibre layer
|
|
noise = rng.gauss(0, 1)
|
|
logit = -6.0 + 0.045 * age + 0.20 * iop + 4.2 * cdr - 0.035 * rnfl + 0.5 * noise
|
|
pos = rng.random() < 1 / (1 + math.exp(-logit))
|
|
rows.append({
|
|
"sample_id": f"S{p:03d}_{e}",
|
|
"patient_id": f"P{p:03d}",
|
|
"age": round(age, 1),
|
|
"iop": round(iop, 1),
|
|
"cdr": round(cdr, 3),
|
|
"rnfl": round(rnfl, 1),
|
|
"noise_feat": round(noise, 3),
|
|
"site": site,
|
|
"notes": "routine screening" if not pos else "referred",
|
|
"diagnosis": "glaucoma" if pos else "healthy",
|
|
})
|
|
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
with out.open("w", newline="") as fh:
|
|
w = csv.DictWriter(fh, fieldnames=list(rows[0]))
|
|
w.writeheader()
|
|
w.writerows(rows)
|
|
|
|
pos = sum(r["diagnosis"] == "glaucoma" for r in rows)
|
|
print(f"wrote {out} rows={len(rows)} patients={N_PATIENTS} positives={pos} ({pos/len(rows):.0%})")
|