Can routine measurements screen for heart disease?
A classification study on 303 referred cardiac patients — and why the best-scoring model is not the one served.
Thirteen clinical measurements, 303 patients, and one question: how much of the diagnosis is visible before any invasive test is ordered? The study compares candidate models against a baseline, drops the two features that would have made it a better scorer and a worse screening tool, and reports what 76 test rows can and cannot establish.
- Reading time:
- 11 min
- Published:
- 15 Sep 2026
- Updated:
- 15 Sep 2026
- Dataset:
- UCI Heart Disease — Cleveland
- Model:
- v1.0.0
The question
Coronary artery disease is confirmed by angiography — an invasive procedure. Before it is ordered, a clinician already has a patient’s age, blood pressure, cholesterol, resting ECG, and how they responded to an exercise test.
How much of the eventual diagnosis is already visible in those routine measurements?
This matters because the answer decides what a model like this could ever be for. If routine measurements carry most of the signal, a model built on them could help prioritise who gets referred. If they do not, then any model that scores well is quietly relying on tests that are only ordered once disease is already suspected — and it is answering a question that has already been answered.
The dataset here contains both kinds of feature, which makes the distinction testable rather than rhetorical.
What this study cannot answer
It cannot say whether any measurement causes heart disease. It cannot establish what would happen to a patient outside this sample. And it cannot produce a diagnosis — the label being predicted is the outcome of an angiogram that has already happened.
The data
303 patients referred for cardiac evaluation at the Cleveland Clinic Foundation in the late 1980s. Thirteen measurements each, plus an angiographic diagnosis.
The full feature dictionary, licence, checksum and collection limitations are on the dataset page.
The leakage audit
Every supervised study here runs a leakage audit before any model is fitted, and training aborts if a feature correlates 0.90 or above with the target. Nothing did — the strongest was thal at +0.538. No duplicate rows, no constant columns, no entity identifier that could span a split, no temporal ordering.
But the audit surfaced something the correlation check alone would have missed.
ca (vessels coloured by fluoroscopy) and thal (thallium stress test) are results of invasive follow-up tests. They precede the diagnosis label, so they are not leakage in the strict sense. They are also not available at the moment a screening decision is made — they are ordered because disease is already suspected. ca in particular is close to a direct measurement of the thing being predicted.
A model using them does not screen. It reports what the catheterisation found.
So the study trains two variants: one with everything, and a screening variant with ca and thal dropped. The screening variant is the one served below.
UCI Heart Disease — Cleveland
CC BY 4.0- Rows
- 303
- Columns
- 14
Collection limitations. Single institution, single era (Cleveland Clinic Foundation, late 1980s). A referral population with 45.9% disease prevalence, far above a general population — probabilities are conditioned on having been referred. Predominantly male. No socioeconomic, comorbidity or medication data.
What the data looks like
Class balance. 164 patients without angiographic disease, 139 with — a 45.9% positive rate. Close to balanced, which is unusual for a clinical dataset and is a direct consequence of this being a referred population rather than a screened one. It means accuracy is less actively misleading here than it would be on a rare outcome, though it is still not the metric that carries the decision.
Missingness. Six values across 303 rows, confined to ca (4) and thal (2) — the two invasive features, which is itself consistent with those tests not always having been performed.
Strongest associations with the diagnosis, on the training split:
| Feature | Correlation with target |
|---|---|
thal (thallium test) |
+0.538 |
ca (vessels by fluoroscopy) |
+0.471 |
thalach (max heart rate) |
−0.413 |
cp (chest pain type) |
+0.412 |
exang (exercise angina) |
+0.408 |
The two invasive tests top the list, which is exactly what makes them tempting and exactly why they were examined rather than simply used. Below them, the routine exercise-test measurements — maximum heart rate, chest pain type, exercise-induced angina — carry associations of similar magnitude.
Class balance
All 303 patients
What this shows. At 45.9% positive the classes are close to balanced, which is unusual for a clinical dataset and follows directly from this being a referred population rather than a screened one. It is also why accuracy is less actively misleading here than it would be on a rare outcome — though still not the metric that carries the decision.
Show the numbers
| Diagnosis | Patients |
|---|---|
| No disease | 164 |
| Disease | 139 |
Source: Training run screening-v1
How the problem was framed
Supervised binary classification. The unit of observation is one patient at one evaluation.
Target. num records 0 for no disease and 1–4 for increasing angiographic severity. The study binarises to presence versus absence. This is the conventional framing for this dataset, and it discards real information: a borderline case and a severe case become the same label. That is a choice the study made, not a property of the data.
Validation. A stratified 75/25 split with a fixed seed, giving 227 training and 76 test rows. Stratified because 303 rows are few enough that an unstratified split can shift test-set prevalence enough to move recall by several points on its own.
Model selection used 5-fold cross-validated recall on the training split only. The test split was evaluated once, at the end. Repeatedly consulting a 76-row test set while choosing between models would turn it into a second training set.
The metric that decides. Recall on the positive class. In a screening frame, a missed case costs more than an additional referral — so a model that trades precision for recall is making the right trade, within reason. Accuracy is reported, never headlined.
Preprocessing
All preprocessing lives inside the serialised pipeline, so the model served below cannot apply a different transformation from the one it was trained with. This is not a stylistic preference: a preprocessing step applied separately at inference time is one of the few bugs that produces plausible wrong answers rather than an error.
| Step | Applied to | Choice |
|---|---|---|
| Imputation | numeric | Median |
| Imputation | categorical | Most frequent |
| Scaling | numeric | Standardisation |
| Encoding | categorical | One-hot, unknown categories ignored |
Imputation statistics are fitted on the training split only. Fitting them on the full dataset before splitting is the most common quiet form of leakage — the test rows contribute to a median that is then used to fill training rows.
Unknown categories are ignored rather than raising, because a visitor to the lab below can submit a combination that 227 training rows never contained, and a server error is the wrong answer to that. The cost is recorded in the limitations: such an input is extrapolated silently.
Candidate models
Majority-class baseline. Predicts ‘no disease’ for everyone. It exists because without it, no other number means anything. It achieves 53.9% accuracy and 0.000 recall — a clean illustration of why accuracy was not chosen as the headline metric.
Logistic regression (C=1.0, class_weight='balanced'). Considered because its coefficients are directly inspectable, it is well suited to 227 rows, and it produces probability estimates natively. Assumes a linear relationship between features and log-odds — a real constraint, taken deliberately.
Random forest (300 trees, min_samples_leaf=3, balanced). Considered because it captures interactions and non-linearities logistic regression cannot. Risks overfitting a small sample and is harder to explain.
Both use balanced class weights so the slightly smaller positive class is not under-weighted. This has a consequence that matters later: balanced weighting deliberately distorts predicted probabilities away from the true base rate, which is why an explicit calibration step follows.
Evaluation
On the held-out test split of 76 patients. Intervals are 95% percentile bootstrap, 2000 resamples.
The comparison
See the table below. Then read this, because the table is easy to misread:
None of these models is distinguishable from the others. Logistic regression scores 0.886 recall and the random forest 0.857, but the intervals are [0.7778, 0.9737] and [0.7333, 0.9677] — they overlap almost entirely. With 76 test rows, a single patient moving from one side of the threshold to the other shifts recall by more than a point.
What the numbers do establish is that every candidate is far better than the baseline, which is a much weaker claim than a leaderboard implies, and the honest one.
Why logistic regression was selected
Not because it scored highest. It was selected because when scores are indistinguishable, the tiebreakers are interpretability, robustness on a small sample, and cost to serve — and it wins all three. Choosing the top row of a table whose rows are statistically identical is how a model gets picked for noise.
Confusion matrix
Of 35 patients who had disease, the model identified 30 and missed 5. Of 41 who did not, it correctly cleared 36 and flagged 5 who were disease-free.
In a screening frame those five false positives are the acceptable cost of the trade. The five false negatives are the number that matters, and the limitations section says who they tend to be.
Did dropping the invasive tests hurt?
Measurably, no. The full-feature variant reached 0.857 recall; the screening variant reached the same, with overlapping intervals. On this sample the routine measurements carry the available signal. That result is what justifies serving the screening model — it gives up nothing detectable and answers a question worth asking.
Recall
0.857
95% CI [0.7333, 0.9677] · the headline metric
Precision
0.857
Positive class, test split
ROC AUC
0.925
Ranking quality
Brier score
0.117
Calibration — lower is better
| Model | Recall | 95% CI | Precision | Accuracy | ROC AUC |
|---|---|---|---|---|---|
| Majority-class baseline Baseline | 0.000 | — | 0.000 | 0.539 | 0.500 |
| Logistic regression | 0.886 | [0.7778, 0.9737] | 0.861 | 0.882 | 0.923 |
| Random forest | 0.857 | [0.7333, 0.9677] | 0.811 | 0.842 | 0.927 |
| Logistic regression, calibrated Selected | 0.857 | [0.7333, 0.9677] | 0.857 | 0.868 | 0.925 |
Every candidate's interval overlaps every other candidate's. The selected row is not the highest scorer; it is the most defensible one when the scores cannot be told apart.
ROC curves
Held-out test split, 76 patients
What this shows. Both models rank cases far better than chance and almost identically to each other. The gap between the two curves is smaller than the uncertainty in either of them at 76 test rows — which is the same reason the model comparison table cannot be read as a ranking.
Show the numbers
| Model | ROC AUC |
|---|---|
| Logistic regression | 0.9233 |
| Random forest | 0.9275 |
Source: Training run screening-v1
Confusion matrix
Selected model on the test split
What this shows. The 5 false positives are the acceptable cost of a screening trade — each is an extra referral. The 5 false negatives are the number that matters, and the limitations section identifies who they tend to be.
Show the numbers
| Actual | Model says | Patients |
|---|---|---|
| No disease | Cleared | 36 |
| No disease | Flagged | 5 |
| Disease | Cleared | 5 |
| Disease | Flagged | 30 |
Source: Training run screening-v1
What the model leans on
Logistic regression coefficients are directly inspectable, which is most of why it was chosen. The served model reports the inputs it weighs most heavily alongside each prediction.
One caveat is stated wherever those weights appear: they are global importances for the fitted model, not an attribution for the individual prediction on screen. A feature with a large coefficient influenced the model’s overall behaviour; it did not necessarily drive this particular result. Per-prediction attribution would need SHAP or a comparable method, which is not justified for a linear model on eleven features.
On calibration
The model is explicitly calibrated using Platt scaling, and the reason is specific: class_weight='balanced' deliberately shifts the decision boundary toward the positive class, which pushes raw predicted probabilities away from the population base rate. Without correction the numbers would rank cases correctly while being wrong as probabilities.
Reported honestly: the calibrated Brier score (0.117) is not better than the uncalibrated logistic regression’s (0.111). At 76 test rows that difference is noise. The calibration step is kept because it is the theoretically correct response to the class weighting, not because the evidence shows it helped.
And the probability remains calibrated to a referred population with 45.9% prevalence — not to a general one.
What the model weighs
Absolute coefficient magnitude, one-hot terms folded back onto their source column
What this shows. Chest pain type dominates, which matches clinical intuition for this population. These are global weights for the fitted model — they describe how it behaves overall, not why it produced any particular prediction.
Show the numbers
| Input | Coefficient magnitude |
|---|---|
| Chest pain type | 2.32 |
| Sex | 1.35 |
| ST segment slope | 0.86 |
| Resting ECG | 0.51 |
| Max heart rate | 0.50 |
| ST depression | 0.46 |
| Exercise angina | 0.43 |
| Fasting blood sugar | 0.38 |
Source: Training run screening-v1
Try it
The screening model, running live. Every input is a measurement available before any invasive test — that is the whole point of this variant.
The output is a class label and a calibrated probability, the decision threshold that produced it, and the model’s semantic version. Read the probability as “among referred cardiac patients in this 1988 sample with similar measurements, roughly this proportion had angiographic disease.” It is not a statement about you, and the ranges below are the ranges the model actually saw.
What this does and does not show
What was observed. Among 303 referred cardiac patients, routine pre-angiographic measurements — chest pain type, exercise-induced angina, maximum heart rate, ST depression — are associated with angiographic disease strongly enough that a linear model separates the classes well on held-out data.
What the model does. It identifies 86% of positive cases in the test split, with a 95% interval of [0.7333, 0.9677], at 86% precision.
What is inferred. That the invasive tests were not carrying the signal alone. Removing both cost nothing measurable, which suggests the routine measurements already contain most of what this dataset has to offer.
What cannot be concluded.
- That any measurement causes heart disease. This is an observational association, not a causal design.
- That logistic regression is better than a random forest for this problem. The confidence intervals say those models are indistinguishable here.
- That these numbers would hold in any other population, era, or care setting.
- That a probability from this model applies to an individual outside the referred population it was fitted on.
The most useful result is not the recall figure. It is that examining when a feature becomes available changed which model got deployed — and cost nothing to do.
Limitations
Ordered by how much each should change your reading of the result.
The two that matter most: the test set has 76 rows, so every interval here is wide enough that model comparisons do not survive it; and this is a referral population, so the probabilities are conditioned on someone having already decided this patient warranted cardiac evaluation.
-
76 test rows — the intervals are wider than the numbers look
Recall's 95% interval spans nearly a quarter. Every model compared falls inside every other model's interval, so no claim that one is better than another is supported. Model selection rested on interpretability for exactly this reason.
-
A referral population, not a screening population
Prevalence in this sample is 45.9%; in an unselected population it is a small fraction of that. A calibrated probability here is calibrated to referred patients and would over-predict substantially elsewhere — and no amount of internal validation would reveal that.
-
Recall is materially worse in younger patients
0.714 for patients aged 56 or under (n=40) against 0.952 for those over 56 (n=36). Age is itself a risk factor and the model appears to lean on it, making it least reliable precisely where a screening tool would need to be most reliable. Subgroup counts are small, so this is indicative rather than precise.
-
One institution, one era
Cleveland Clinic Foundation, late 1980s. Diagnostic criteria, measurement practice and treatment have all moved since. External validity is unestablished, not merely unmeasured.
-
The target collapses four severity levels into one bit
A borderline case and a severe case become the same label. This is the conventional framing and it makes the problem tractable at n=303, but it discards information the data contains.
-
Calibration is theoretically motivated, not demonstrated
Platt scaling is applied because balanced class weighting de-calibrates raw output. But the calibrated Brier score (0.117) is not better than the uncalibrated one (0.111); at this sample size the step cannot be shown to have helped.
-
Unknown inputs extrapolate silently
The encoder ignores category values absent from training rather than failing. A lab visitor can submit a combination the model never saw and receive a confident-looking number with no indication of it.
-
This is not a medical device
An educational demonstration. Not diagnostic, no regulatory clearance, never prospectively validated, and not a basis for any decision about a real person.
Recall by subgroup
Test split only — small counts, indicative not precise
What this shows. Recall falls to 0.714 for patients aged 56 and under against 0.952 for those over 56. Age is itself a risk factor and the model appears to lean on it, so it is least reliable exactly where a screening tool would need to be most reliable. These subgroup counts are small — read the direction, not the decimals.
Show the numbers
| Subgroup | Recall |
|---|---|
| Female (n=26) | 1.000 |
| Male (n=50) | 0.808 |
| Age 56 and under (n=40) | 0.714 |
| Over 56 (n=36) | 0.952 |
Source: Training run screening-v1
References
Sources are listed below.
Concepts in this study
Recall — of everyone who actually had the condition, the share the model found. It is the metric that carries the decision here because a missed case costs more than an extra referral.
Precision — of everyone the model flagged, the share who actually had it. Recall and precision trade against each other; moving the threshold buys one with the other.
Calibration — whether a predicted probability of 0.7 corresponds to the event happening about 70% of the time. A model can rank cases perfectly and still be badly calibrated, which is why ranking metrics like AUC and calibration metrics like the Brier score are both reported.
Class weighting — telling the model to treat errors on the smaller class as more costly. It improves recall and, as a side effect, de-calibrates the raw probabilities, which is why a calibration step follows it here.
Target leakage — a feature that encodes the answer. The strict form is caught by a correlation check. The form that caught this study is subtler: a feature legitimately measured before the label, but not available at the moment the prediction would need to be made.
Bootstrap confidence interval — resampling the test set with replacement to see how much a metric moves. On 76 rows it moves a great deal, which is the single most important thing to know about every number on this page.
Glossary
- Recall Theory in Axiomizer →
- Of everyone who actually had the condition, the share the model found. It is this study's headline metric because a missed case costs more than an extra referral.
- Precision Theory in Axiomizer →
- Of everyone the model flagged, the share who actually had the condition. Precision and recall trade against each other as the decision threshold moves.
- Calibration Theory in Axiomizer →
- Whether a predicted probability of 0.7 corresponds to the event happening about 70% of the time. A model can rank cases perfectly and still be badly calibrated.
- Brier score Theory in Axiomizer →
- The mean squared error of predicted probabilities. Lower is better. It measures calibration, which ranking metrics like AUC do not.
- Class weighting Theory in Axiomizer →
- Telling the model that errors on the smaller class cost more. It raises recall and, as a side effect, de-calibrates the raw probabilities.
- Target leakage Theory in Axiomizer →
- A feature that encodes the answer. The strict form shows up as a very high correlation with the target; the subtler form is a feature that is legitimate but unavailable at the moment the prediction would need to be made.
- Bootstrap confidence interval Theory in Axiomizer →
- Resampling the test set with replacement to see how much a metric moves. On 76 rows it moves a great deal, which is the most important thing to know about these numbers.
- Baseline Theory in Axiomizer →
- The simplest possible predictor — here, always guessing the majority class. Without it, no other score means anything.
Check your understanding
6 questions on the reasoning in this study. Every answer comes with an explanation, and no account is needed.
Take the quizReferences
- Janosi, A., Steinbrunn, W., Pfisterer, M., & Detrano, R. (1989). Heart Disease [Dataset]. UCI Machine Learning Repository. doi:10.24432/C52P4X Link (accessed 14 Sep 2026)
- Detrano, R., Janosi, A., Steinbrunn, W., et al. (1989). International application of a new probability algorithm for the diagnosis of coronary artery disease. American Journal of Cardiology, 64(5), 304-310. doi:10.1016/0002-9149(89)90524-9 (accessed 14 Sep 2026)
- Platt, J. (1999). Probabilistic outputs for support vector machines and comparisons to regularized likelihood methods. Advances in Large Margin Classifiers. (accessed 14 Sep 2026)
- Pedregosa, F., et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research, 12, 2825-2830. Link (accessed 14 Sep 2026)
Reproducibility
- Model version
- 1.0.0
- Artifact checksum
- 92edd6e3e114ad0c…
- seed
- 42
- python
- 3.13.5
- git_sha
- 8e67a8a
- trained_at
- 2026-09-15T01:06:22.900488+00:00
- scikit_learn
- 1.6.0
- dataset_checksum
- a74b7efa387bc9d108d7d0115d831fe9b414b29ae7124f331b622b4efa0427c8
Discussion
Sign in to join the discussion. Reading needs no account.
No comments yet.