-
-
The core question: is a sepsis model reading physiology or clinician behavior?
-
Cross-hospital transfer: process-only features drop sharply (0.76 → 0.59) when moved, while physiology holds up better.
-
One patient's timeline with all information: physiology plus process signals.
-
The same patient with process features only: measurement patterns, no values.
-
Stress test: model accuracy as the target hospital measures less.
Inspiration
Hospital data records more than how sick a patient is. It also records what clinicians decided to do: which labs they ordered, when, how often, and when they stopped checking.
That matters for sepsis prediction. A sicker patient gets more tests, so "how many tests were ordered" can predict sepsis without the model learning anything about physiology. But ordering habits are set by local workflow, and they differ between hospitals. A model that leans on them may look strong in one hospital and fall apart in another.
We were motivated by published external evaluations of deployed sepsis models (for example, Wong et al. on the Epic Sepsis Model) and by work on informative missingness in health records. We do not reproduce those studies. We wanted to ask a narrower, testable question of our own:
When a clinical model predicts sepsis, is it seeing the patient, or seeing what clinicians chose to measure?
What it does
Clinician's Shadow is an empirical audit, not a sepsis predictor. It splits the information a model can see into two families:
- Physiology: what was happening to the patient (vitals and lab values).
- Process: what clinicians chose to measure (which variables were observed, how recently, how often, how large the lab panel was), with no values at all.
It trains matched models on each family and then moves them between two hospitals. Four models share identical hyperparameters and always see the same four basic features (age, gender, hospital-admission time, ICU hours):
- \( M_{\text{BASE}} \): base features only
- \( M_{V} \): base + physiology
- \( M_{P} \): base + process
- \( M_{VP} \): base + both
We evaluate within each hospital (patient-grouped cross-validation) and across hospitals, \( A \to B \) and \( B \to A \), using AUROC, AUPRC, Brier score and calibration error, with 95% confidence intervals from a patient-level bootstrap.
A static research viewer shows the transfer matrices, a patient timeline where you can toggle between all information, physiology only and process only (clearly labeled as model-input counterfactuals, not clinical ones), and a measurement-thinning stress test.
What we found (subset run, first 3,000 patients per hospital):
- Within a hospital, process alone is highly predictive. \( M_P \) reaches AUROC 0.757 on A and 0.764 on B, well above \( M_{\text{BASE}} \) (0.661 and 0.674).
- Across hospitals, that advantage is not detectable. \( M_P \) transfers at 0.592 (\( A \to B \)) and 0.580 (\( B \to A \)), statistically indistinguishable from \( M_{\text{BASE}} \) at 0.599 and 0.598.
- Physiology keeps some of its gain (\( M_V \): 0.677 and 0.662) but also degrades. It falls about 0.11 AUROC from within-B to \( A \to B \), so it does not transfer cleanly either.
- Combining both does not help under shift. \( M_{VP} \) is best within a hospital but no better than \( M_V \) after transfer.
We report these as observed. They do not show that the model is biased, that physiology transfers well, or that process reliance causes the drop. Two hospitals confound measurement practice with case mix and labeling.
How we built it
Data. The public, de-identified PhysioNet/CinC Challenge 2019 dataset: hourly ICU records from two hospital systems, Set A (Beth Israel Deaconess) and Set B (Emory). Because of download and compute limits, we ran on the first 3,000 patients of each: 116,651 and 113,642 hourly rows, with 270 and 177 septic patients.
Feature families that cannot be mixed. Every column carries its source as a prefix (b__, v__, p__), and the code enforces it by function signature:
- The process function receives only the boolean observation mask, so it cannot read a value.
- The physiology function receives only values.
- Process features include per-variable "observed now", "hours since last measurement", cumulative counts, plus panel size and 6-hour observation density: 111 columns in all, against 34 for physiology.
Temporal correctness. Every feature at hour \( t \) uses only rows \( \le t \) of that patient. Physiology is last-observation-carried-forward. Windowed counts use \( \text{cnt}t - \text{cnt}{t-6} \). Tests truncate a patient's record and check that earlier features do not change.
Avoiding a missingness sentinel. A constant fill such as the median acts as a "not measured yet" flag that a tree can split on. Before a variable's first measurement we instead fill with a seeded per-patient draw from the training distribution, with no missingness-indicator columns anywhere.
Validation. Stratified, patient-grouped 5-fold cross-validation within each hospital, transfer in both directions, and a bootstrap that resamples patients, not rows. Imputer parameters are fit only on the training side of each split.
Attribution (Process Reliance Index). For the combined model we replace one family at a time with a fixed reference and measure the change in log-odds:
$$ c_P = \operatorname{logit} f(x) - \operatorname{logit} f\big(x_{P \leftarrow r}\big), \qquad c_V = \operatorname{logit} f(x) - \operatorname{logit} f\big(x_{V \leftarrow r}\big) $$
$$ \mathrm{PRI} = \frac{c_P}{c_P + c_V} \quad \text{when } c_P > 0 \text{ and } c_V > 0 $$
This is a model-based, associational diagnostic, not causal inference.
Stack. Python, NumPy, pandas and scikit-learn (gradient boosting plus a logistic-regression baseline), exported to JSON and shown in a dependency-free HTML/JS/SVG viewer with no backend and no retraining.
Challenges we ran into
- Keeping "physiology" honest. We audited our own physiology family by asking whether it alone can predict that a lab is being measured. It can: AUROC 0.72 on A and 0.92 on B. We tested one suspect, interpolated imputation draws, by replacing them with real observed values; the audit barely moved (0.717 and 0.915). So the physiology family is values-only, not provably process-free. Part of that signal is probably genuine illness severity, and we cannot separate it from ordering behavior here. We kept that result in the write-up rather than hiding it.
- Temporal leakage. It is easy to leak the future with a forward fill or a rolling window. We wrote tests that edit data after time \( t \) and confirm features at or before \( t \) are unchanged.
- Off-distribution ablations. Replacing a feature family with a fixed reference produces inputs the model never saw. In one transfer direction the index was defined for only about 16% of sampled rows, and in many patients removing either family flattens the prediction. We treat the attribution as illustrative only.
- Rare positives. With around 1.5% to 2.2% of rows labeled positive, AUPRC is low in absolute terms and the confidence intervals are wide.
- Build constraints. Our build environment had no network access and a single CPU core, so we sized the experiment to what we could run and verify, and said so in the README.
Accomplishments that we're proud of
- A hypothesis test that was allowed to fail. Part of the story (process helps locally, adds nothing detectable after transfer) is supported, and part (a unique fragility of process models) is not.
- Leakage tests that check the claims directly: physiology is invariant to redundant measurements, process is invariant to scrambled values, patients never straddle a split, and labels stay aligned with the source files.
- A fully reproducible pipeline with seeded randomness, machine-readable results, and a viewer that reads precomputed JSON.
- Reporting limitations in the README next to the results, including the audit result that weakens our own physiology model.
What we learned
- "Values only" is not the same as "process free". Values and measurement timing are entangled in clinical data, and a representation has to be audited, not assumed.
- In-hospital performance can come from workflow, not physiology. That is exactly why cross-hospital validation matters.
- Negative and mixed results are still results. The honest finding here is more useful than a clean story we could not defend.
- A diagnostic is only as trustworthy as its reference point. Group ablation is easy to compute and easy to over-read.
What's next for Clinician's Shadow
- Run on the full dataset (about 20,000 patients per hospital) for much tighter intervals.
- Add paired bootstrap tests for model-to-model differences instead of comparing separate intervals.
- Try per-hospital process normalization, such as within-hospital percentiles, to see whether relative measurement behavior transfers better than absolute counts.
- Test on more hospitals and settings, since two systems cannot separate practice from population.
- Replace fixed-reference ablation with conditional or Shapley-style attribution that stays closer to the data distribution.
- Add a stronger leakage control, such as an adversarial check that physiology features cannot predict measurement timing.
Research prototype built on public, de-identified data for GIBC V2, Track 02. Not a clinical tool.
Built With
- claude
- css3
- git
- gradient-boosting
- html5
- javascript
- json
- logistic-regression
- numpy
- pandas
- physionet
- pytest
- python
- scikit-learn
- svg
- vercel
Log in or sign up for Devpost to join the conversation.