flowchart LR C[Care and workflow] --> M[Measurement] M --> R[Clinical record] R --> T[Analysis table] T --> P[Model or estimate] P --> E[Evidence and uncertainty] E --> D[Decision and action]
Lec 2: Clinical data science foundations: from care to evidence
DGIN 5401 · Dalhousie University
flowchart LR C[Care and workflow] --> M[Measurement] M --> R[Clinical record] R --> T[Analysis table] T --> P[Model or estimate] P --> E[Evidence and uncertainty] E --> D[Decision and action]
Practical
Clinical and scientific
Every transformation changes what the data can mean and what claim the result can support.
flowchart LR A[Patient and circumstances] --> B[Access and care-seeking] B --> C[Clinical workflow and measurement] C --> D[Documentation and coding]
flowchart LR D[Documentation and coding] --> E[Database extraction] E --> F[Cohort and label construction] F --> G[Training table] G --> H[Model]
At each transition: selection, measurement, missingness, and institutional policy can alter what is visible to the modeller. (Agniel, Kohane, and Weber 2018; Suresh and Guttag 2021)
The registry and EHR were not created to test your hypothesis.
Data principle
The data-generation process is part of the model, even if you didn’t mean it to be.
Pulse oximetry is an input to care and to many predictive pipelines.
In one hospital cohort, when pulse oximetry displayed 92–96%, occult hypoxemia was present in 11.7% of Black patients and 3.6% of White patients
A model trained on sensor values can inherit measurement error, treatment differences caused by that error, and labels produced downstream.
Audit the instrument
Before asking whether the model is ‘fair’, ask whether the measurement process is valid across the people and environments in which it will be used.
Across one episode, available data may include:
Public resources such as 🔗 MIMIC and 🔗 PhysioNet illustrate some breadth and complexity of clinical data, though they can also be limited. (Johnson et al. 2023; Goldberger et al. 2000)
Common terminologies include:
Check version, granularity, mapping, and whether a code records a true event, a rule-out diagnosis, a billing choice, or a documentation habit.
Notes may reveal:
They also contain templates, copied text, shorthand, inconsistent terminology, and information written after the prediction cut-off. (Rule et al. 2021; Davis et al. 2024)
Pre-operative consult
Patient reports independent mobility at baseline. HbA1c has improved, but home glucose remains variable. No active skin lesions. Discussed sternal precautions and expected recovery.
Operative note
Combined CABG and valve procedure. Prolonged bypass due to difficult exposure. Rewarming uncomplicated. No gross contamination. Skin closed at 18:42.
Ward nursing note
Incision dry at morning assessment. Later noted mild inferior erythema without drainage. Patient afebrile; pain increased with cough. Team notified.
Follow-up message
Patient sent photograph through portal. Reports new drainage and fatigue since yesterday. Community prescription started; asked to attend urgent review.
Pause and discuss
Which fragment is available at each proposed prediction time? Which terms require context, negation, or temporal interpretation?
A “patient row” may actually represent a patient, admission, procedure, specimen, device episode, note, or measurement.
| Key | Possible unit |
|---|---|
patient_id |
person |
admission_id |
hospitalization |
surgery_id |
operation |
specimen_id |
microbiology sample |
note_id |
document |
measurement_id |
one recorded value |
Join audit
Before joining, state the expected relationship: one-to-one, one-to-many, or many-to-many. Then verify row counts and duplicate keys. Accidental row multiplication can manufacture sample size and leakage.
Long form
| surgery_id | time | creatinine |
|---|---|---|
| A01 | pre-op | 82 |
| A01 | ICU+6h | 104 |
| A01 | ICU+24h | 131 |
Natural for irregular measurements and trajectories.
Wide form
| surgery_id | cr_pre | cr_6h | cr_24h |
|---|---|---|---|
| A01 | 82 | 104 | 131 |
Convenient for some models, but requires explicit windows and missing-value rules.
The transformation from long to wide quietly chooses which measurements count, how they are summarized, and what time means.
For a laboratory result, distinguish:
Prediction-time rule
Features must be filtered by the time they were available, not merely by the date attached to the observation.
The same distinction applies to notes, diagnoses, orders, and discharge summaries.
| Snapshot | When is the prediction made? | Information available | Course role |
|---|---|---|---|
| T0 | Before the operation begins | Preoperative history, status, labs, planned procedure, and preoperative note | Required shared backbone |
| T1 | At CVICU arrival | T0 plus performed procedure, intraoperative support and events, and handoff note | Optional dynamic update |
| T2 | Approximately 24 h after surgery | T0/T1 plus early postoperative events and available POD1 note | Optional landmark analysis |
An information boundary, not just a timestamp
A predictor may enter a model only if it could have been available to the intended user by that snapshot.
The snapshot does not define the outcome horizon. At each later landmark, specify who remains at risk and what future outcome is still being predicted.
Not prediction features
Discharge and outcome-only information cannot enter T0 or T1. Identifiers, split labels, and synthetic-era fields are for linkage, audit, or evaluation.
Common failure modes include:
A useful audit table
For every important variable, record: meaning, unit, valid range, source system, timestamp semantics, missing-value codes, and known version changes.
| surgery_id | age | diabetes | bypass_minutes | preop_hgb | SSI_30d |
|---|---|---|---|---|---|
| A001 | 67 | 1 | 121 | 119 | 0 |
| A002 | 54 | 0 | 88 | 137 | 0 |
| A003 | 73 | 1 | 176 | 108 | 1 |
| … | … | … | … | … | … |
rows = surgeries / observations
columns = measurements, codes, or derived features
X = feature matrix
y = target / outcome
f(X) = model scores or predictions
ŷ = prediction
loss = training objective we aim to minimize (e.g., error)
metric = evaluation summary (e.g., accuracy)Four questions before code
What is the row unit? Which columns exist by the prediction time? Which columns are actions or consequences?
A practical audit asks about:
A more recent systematic framework organizes data-quality dimensions for trustworthy medical AI. (Schwabe et al. 2024)
M, male, 1;-1;Pause and discuss
Which problems can be detected mechanically? Which require domain knowledge or source-system review?
Let \(M_j=\mathbb{1}(X_j\text{ was observed})\). In clinical data, \(M_j\) may encode:
Patient and care process
Local measurement system
The model can therefore predict from who gets measured, when, and where, not only from the measured value. Test-ordering patterns can be as predictive as, or more predictive than, the test results themselves. (Agniel, Kohane, and Weber 2018; Groenwold 2020)
Useful internally does not mean transportable
A missingness indicator may improve internal validation while learning a local observation policy. If ordering or recording practices change, \(P(Y\mid M_j)\) can change and model performance can deteriorate. (Groenwold 2020; Nestor et al. 2019)
Audit: report missingness by outcome, service or site, era, and subgroup; compare models with and without missingness features; validate temporally and externally.
There may be no perfect “gold standard.” SSI adjudication can depend on notes, cultures, procedures, imaging, and follow-up.
For two raters, Cohen’s kappa adjusts observed agreement for expected agreement:
\[ \kappa=\frac{P(A)-P(E)}{1-P(E)}. \]
Risk
A single agreement number can conceal systematic disagreements about borderline cases, severity, timing, or subgroups. Review disagreement patterns, not only kappa.
Suppose two reviewers independently adjudicate 100 postoperative cases:
| Reviewer B: SSI | Reviewer B: no SSI | Total | |
|---|---|---|---|
| Reviewer A: SSI | 3 | 3 | 6 |
| Reviewer A: no SSI | 1 | 93 | 94 |
| Total | 4 | 96 | 100 |
Observed agreement
\[ P(A)=\frac{3+93}{100}=0.96 \]
Agreement expected from the marginal label rates
\[ P(E)=\left(\frac{6}{100}\frac{4}{100}\right) +\left(\frac{94}{100}\frac{96}{100}\right) =0.9048 \]
Cohen’s kappa
\[ \kappa=\frac{0.96-0.9048}{1-0.9048}\approx0.58 \]
Class-specific agreement
\[ P_{pos}=\frac{2(3)}{2(3)+3+1}=0.60,\qquad \\ P_{neg}=\frac{2(93)}{2(93)+3+1}\approx0.979 \]
Why 96% is not the whole story
Most agreement comes from the common negative class. Inspect the cross-tabulation, positive and negative agreement, and the actual disputed cases—not only \(\kappa\). Agreement also does not establish label validity. (Cohen 1960; Feinstein and Cicchetti 1990; Cicchetti and Feinstein 1990)
| Rating design | Measure(s) to consider | What to make explicit |
|---|---|---|
| Two raters; nominal categories | Cohen’s \(\kappa\) | Full cross-tabulation, prevalence by rater, and class-specific agreement |
| Two raters; ordered categories | Weighted \(\kappa\) | Weighting scheme; adjacent and extreme disagreements need not count equally |
| More than two raters; nominal categories | Fleiss’ \(\kappa\) | Rater-sampling design and whether each case has the intended number of ratings |
| Varying raters, missing ratings, or several measurement levels | Krippendorff’s \(\alpha\) | Distance function appropriate to nominal, ordinal, interval, or ratio data |
| Strongly imbalanced categorical labels | Gwet’s AC1 or AC2 as a sensitivity analysis | It uses a different chance-agreement model; do not treat it as a drop-in “fixed kappa” |
| Continuous ratings from raters | Intraclass correlation coefficient | ICC form: absolute agreement vs consistency; single vs average rating; fixed vs random raters |
| Two continuous measurement methods | Bland–Altman analysis | Bias and limits of agreement; correlation is not agreement |
Methods: (Cohen 1968; Fleiss 1971; Hayes and Krippendorff 2007; Gwet 2008; Shrout and Fleiss 1979; Bland and Altman 1986)
Don’t just report a number without context
Describe the label definition, case sampling, rater training, independent-rating procedure, missing ratings, cross-tabulation or disagreement distribution, coefficient with uncertainty, and adjudication process. (Kottner et al. 2011)
Draw a timeline for every candidate predictor.
[T0: before operation] ─ incision ─ bypass ─ skin closure
─ [T1: CVICU arrival] ─ [T2: ≈24 h after surgery]
─ ward ─ discharge ─ day 30Ask:
Risk
A variable named “postoperative antibiotic” may be a response to suspected infection rather than a pre-existing risk factor.
Sampling random rows is unsafe when rows share:
Temporal and geographic splits often provide more realistic tests of transportability than random splits. (Kapoor and Narayanan 2023; Collins et al. 2024)
The STANDING Together recommendations separate:
Document composition, provenance, exclusions, missingness, subgroup representation, intended uses, known limitations, and potential inequities. (Alderman et al. 2025)
Data principle
“No dataset is free of limitations” is not an excuse to not document those limitations.
The course dataset is synthesized so that everyone can inspect, manipulate, and discuss a rich clinical(-ish) registry without exposing real patient records.
Synthetic data can support:
Claim boundary
A result on synthesized data is evidence about the educational or methodological experiment. Don’t assume that your model will perform in real cardiac-surgery care.
Marginal fidelity
Are individual distributions, ranges, and prevalences plausible?
Relational fidelity
Are clinically important associations, interactions, and temporal dependencies preserved?
Workflow fidelity
Do measurement frequency, missingness, documentation, and site or era effects resemble care?
Rare-event fidelity
Are low-frequency outcomes and clinically unusual combinations represented credibly?
Downstream utility
Do conclusions from specified analyses resemble conclusions from an appropriate reference?
Privacy risk
Can records, attributes, or membership be inferred from the synthetic release or its generator?
No single similarity score establishes all six. (Kaabachi et al. 2025)
A generator trained on individual-level records can overfit or leak information about its training set. Exact or near copies are one warning sign, but record-level similarity alone does not establish anonymity. (Alaa et al. 2022; Pilgram, Dankar, et al. 2025)
Utility checks
Privacy and governance checks
No single metric certifies both usefulness and privacy. In a 2025 review, only 46% of studies claiming privacy-preserving use reported any privacy evaluation, and similarity-based checks often underestimated risk. (Kaabachi et al. 2025; Pilgram, Dankar, et al. 2025)
A widely used population-health algorithm predicted future 💸 cost as a proxy for health need.
Because less money was spent on Black patients with the same level of illness, equally sick patients received different risk scores.
Correcting the proxy would have increased the proportion of Black patients selected for additional care from 17.7% to 46.5%. (Obermeyer et al. 2019)
Remember
The model may optimize its stated target correctly while failing the clinical purpose.
Deep-learning systems for COVID-19 chest radiographs appeared accurate but relied on confounding features associated with data source, acquisition, and hospital rather than pathology. Some shortcuts persisted even across external datasets. (DeGrave, Janizek, and Lee 2021)
A harder question than accuracy
What stable mechanism could produce this prediction in a new hospital, scanner, workflow, and patient population?

Average radiographs expose source signatures in laterality markers and image borders. Publisher figure from DeGrave, Janizek, and Lee (2021), Fig. 4.
Representation and access
Who reaches care, enters the registry, has follow-up, and receives the intervention?
Measurement and labels
Whose physiology is measured accurately? What do codes and outcomes actually encode?
Evaluation
Which settings, eras, groups, and workflows are represented in validation?
Deployment and adoption
Who sees the tool, trusts it, is burdened by it, or can contest its consequences?
Fairness metrics can reveal some disparities. They cannot repair an ill-chosen purpose or an inequitable care pathway by themselves.
For a thresholded SSI-surveillance model, let:
| Flagged | Not flagged | |
|---|---|---|
| SSI observed | TP | FN |
| No SSI observed | FP | TN |
Four common comparisons use different denominators:
\[ \begin{aligned} \text{flag rate}_g &= P(\hat Y_t=1\mid G=g)\\ \text{TPR}_g &= P(\hat Y_t=1\mid Y=1,G=g)\\ \text{FPR}_g &= P(\hat Y_t=1\mid Y=0,G=g)\\ \text{PPV}_g &= P(Y=1\mid \hat Y_t=1,G=g). \end{aligned} \]
The denominator is the fairness question
These rates can tell different (and sometimes incompatible) stories. They also treat \(Y\) as the reference label, even when outcome ascertainment or follow-up may differ across groups. (Hardt, Price, and Srebro 2016; Chouldechova 2017)
Demographic parity asks for equal positive-decision rates:
\[ P(\hat Y_t=1\mid G=0)=P(\hat Y_t=1\mid G=1). \]
It may be relevant when
It can mislead when
Clinical interpretation
Equal rates of enhanced-surveillance flags do not, by themselves, establish equitable care. Ask what action follows, who can benefit, and who bears the burden. (Hardt, Price, and Srebro 2016)
Equalized odds requires the positive-decision rate to be equal across groups within each observed outcome class: (Hardt, Price, and Srebro 2016)
\[ P(\hat Y_t=1\mid Y=y,G=0)=P(\hat Y_t=1\mid Y=y,G=1), \qquad y\in\{0,1\}. \]
Among cases with SSI
Equal TPR means the same proportion is flagged. Unequal TPR means one group experiences more missed opportunities for surveillance.
Among cases without SSI
Equal FPR means the same proportion is falsely flagged. Unequal FPR means one group experiences more unnecessary calls, visits, anxiety, or treatment.
Equal opportunity is the one-sided relaxation that equalizes TPR but does not require equal FPR. (Hardt, Price, and Srebro 2016)
Required assumption
The outcome definition, follow-up, and ascertainment must be comparable. Otherwise, error-rate parity may merely equalize performance against a differentially biased reference label.
Thresholded decision: predictive parity
\[ P(Y=1\mid \hat Y_t=1,G=0) =\\ P(Y=1\mid \hat Y_t=1,G=1). \]
Among people who are flagged, the same proportion experiences the observed outcome. This is equality of positive predictive value at the chosen threshold.
Risk score: calibration within groups
\[ P(Y=1\mid \hat p=p,G=g)\approx p. \]
Among people assigned a risk near \(p\), approximately a proportion \(p\) experiences the observed outcome—within each group.
Do not collapse these concepts
Equal PPV at one threshold is not full calibration. Groupwise calibration does not imply equal TPR, equal FPR, equal treatment, or equal clinical utility. (Chouldechova 2017; Kleinberg, Mullainathan, and Raghavan 2017)
Synthetic patient-level data (not AIRO). Each row represents one imagined patient (\(N=1000\)) with an observed SSI outcome and a model risk score.
import numpy as np
import pandas as pd
rng = np.random.default_rng(5401)
threshold = 0.50
# Pedagogical design: same sample size, TPR, and FPR; different prevalence.
design = pd.DataFrame({
"group": ["A", "B"],
"n": [500, 500],
"prevalence": [0.10, 0.04],
"target_tpr": [0.80, 0.80],
"target_fpr": [0.10, 0.10],
})
def make_group(spec):
"""Create patient-level outcomes and scores for one audit group."""
n = int(spec["n"])
n_positive = int(round(n * spec["prevalence"]))
# Individual observed outcomes.
y = np.r_[
np.ones(n_positive, dtype=int),
np.zeros(n - n_positive, dtype=int),
]
rng.shuffle(y)
# Select exactly the requested fraction of positives and negatives to flag.
flagged = np.zeros(n, dtype=int)
positive_idx = np.flatnonzero(y == 1)
negative_idx = np.flatnonzero(y == 0)
n_tp = int(round(spec["target_tpr"] * len(positive_idx)))
n_fp = int(round(spec["target_fpr"] * len(negative_idx)))
flagged[rng.choice(positive_idx, size=n_tp, replace=False)] = 1
flagged[rng.choice(negative_idx, size=n_fp, replace=False)] = 1
# Give each patient a score consistent with a thresholded model decision.
risk_score = np.empty(n)
risk_score[flagged == 1] = rng.uniform(
threshold, 0.95, size=(flagged == 1).sum()
)
risk_score[flagged == 0] = rng.uniform(
0.01, threshold, size=(flagged == 0).sum()
)
return pd.DataFrame({
"group": spec["group"],
"observed_ssi": y,
"risk_score": risk_score,
})
records = pd.concat(
[make_group(spec) for _, spec in design.iterrows()],
ignore_index=True,
)
records.insert(
0,
"patient_id",
[f"SYN-{i:04d}" for i in range(1, len(records) + 1)],
)
records["flagged"] = (records["risk_score"] >= threshold).astype(int)
records = records.sample(frac=1, random_state=5401).reset_index(drop=True)
def audit_group(frame):
"""Compute confusion counts and threshold metrics from patient rows."""
y = frame["observed_ssi"].to_numpy(dtype=bool)
pred = frame["flagged"].to_numpy(dtype=bool)
tp = int((y & pred).sum())
fn = int((y & ~pred).sum())
fp = int((~y & pred).sum())
tn = int((~y & ~pred).sum())
n = len(frame)
return {
"n": n,
"events": int(y.sum()),
"TP": tp,
"FN": fn,
"FP": fp,
"TN": tn,
"prevalence": y.mean(),
"TPR": tp / (tp + fn),
"FPR": fp / (fp + tn),
"flag_rate": (tp + fp) / n,
"PPV": tp / (tp + fp),
"accuracy": (tp + tn) / n,
}
metrics = pd.DataFrame({
group: audit_group(frame)
for group, frame in records.groupby("group", sort=True)
}).T
# Executable checks: fail the render if the constructed example drifts.
assert len(records) == 1000 and records["patient_id"].is_unique
np.testing.assert_allclose(metrics["TPR"], [0.80, 0.80])
np.testing.assert_allclose(metrics["FPR"], [0.10, 0.10])
pct = lambda value: f"{100 * value:.1f}%"
summary = pd.DataFrame(index=[
"Patients",
"Observed SSI cases",
"TP / FN",
"FP / TN",
"TPR",
"FPR",
"Flag rate",
"PPV",
"Accuracy",
])
for group in metrics.index:
m = metrics.loc[group]
summary[f"Group {group}"] = [
f"{int(m['n']):,}",
f"{int(m['events'])} ({pct(m['prevalence'])})",
f"{int(m['TP'])} / {int(m['FN'])}",
f"{int(m['FP'])} / {int(m['TN'])}",
pct(m["TPR"]),
pct(m["FPR"]),
pct(m["flag_rate"]),
pct(m["PPV"]),
pct(m["accuracy"]),
]
summary.index.name = "Quantity"
summary| Group A | Group B | |
|---|---|---|
| Quantity | ||
| Patients | 500 | 500 |
| Observed SSI cases | 50 (10.0%) | 20 (4.0%) |
| TP / FN | 40 / 10 | 16 / 4 |
| FP / TN | 45 / 405 | 48 / 432 |
| TPR | 80.0% | 80.0% |
| FPR | 10.0% | 10.0% |
| Flag rate | 17.0% | 12.8% |
| PPV | 47.1% | 25.0% |
| Accuracy | 89.0% | 89.6% |
Read, verify, then perturb
Equalized odds: TPR and FPR match. Demographic parity: flag rates differ. Predictive parity: PPV differs. Expand the code, inspect records, and then change Group B’s prevalence to 10%: the error rates stay fixed while PPV and the flag rate move.
Let the observed outcome prevalence in group \(g\) be
\[ \pi_g=P(Y=1\mid G=g). \]
At a fixed threshold,
\[ \operatorname{PPV}_g = \frac{\operatorname{TPR}_g\,\pi_g} {\operatorname{TPR}_g\,\pi_g+ \operatorname{FPR}_g(1-\pi_g)}. \]
If two groups have the same TPR and FPR but different prevalences, their PPVs generally differ. Conversely, forcing PPV to match may require unequal error rates, different thresholds, or changes to the underlying data and care process. (Chouldechova 2017; Kleinberg, Mullainathan, and Raghavan 2017)
An incompatibility, not a software defect
With unequal base rates and imperfect prediction, common fairness criteria cannot generally all be satisfied at once. Choosing what to equalize is therefore both a technical and a values decision. (Chouldechova 2017; Kleinberg, Mullainathan, and Raghavan 2017)
Access and allocation
Compare flag or selection rates when the decision distributes an opportunity or scarce resource.
Detection
Compare TPR when missed cases create unequal loss of access to an effective response.
Burden
Compare FPR—or both TPR and FPR—when false alarms create work, anxiety, visits, or treatment.
Meaning and trust
Compare PPV and groupwise calibration when clinicians or patients interpret the output as risk.
Before analysis, specify the groups, outcome window, action, threshold, primary fairness concern, acceptable trade-offs, and uncertainty analysis. Report denominators and event counts. (Hardt, Price, and Srebro 2016; Chouldechova 2017; Kleinberg, Mullainathan, and Raghavan 2017; Rajkomar et al. 2018)
A metric is only evidence
A disparity can identify where to investigate but does not, by itself, establish the cause or determine the remedy.
Design-stage questions include:
Later
We will return to privacy as a technical and governance problem, including federated learning, cryptographic tools, differential privacy, and privacy–utility–fairness trade-offs.
This section reviews the technical grammar needed to read, run, critique, and extend a clinical ML study in practice.
We will refresh
We will keep asking
Use this diagnostically
No one is expected to arrive equally fluent in every area. Notice where the reasoning becomes effortful. See 🔗 supporting resources.
Because scientific results must be reproducible and dependable, record:
Warning
A wrong kernel can produce a plausible answer with the wrong software stack. In clinical data science, that is a provenance error.
conda init, close the shell, and open it again.Why Conda here?
Conda isolates Python and binary-heavy scientific packages. Our course examples use Python 3.11 for a stable, common baseline.
Clinical boundary
A software environment does not make health data safe. Use only approved storage, access, and disclosure processes for any non-synthetic data.
conda create -n dgin5401 -c conda-forge python=3.11 numpy pandas matplotlib scikit-learn jupyterlab ipykernel -y
conda activate dgin5401Then verify the scientific stack:
python -m pip ... or pip install ... inside the activated environment only when a package is unavailable through your chosen Conda channel.Register the activated environment as a Jupyter kernel:
In the notebook, select Python (DGIN 5401) and run:
The executable path should live inside the dgin5401 environment.
base is not the courseThe base environment can become a dumping ground for unrelated projects. Keep course work in its own environment.
# Optional: stop base from activating in every new shell
conda config --set auto_activate_base false
# Start a course session
conda activate dgin5401
# The active environment has an asterisk
conda info --envs
# Both paths should point into dgin5401
python -c "import sys; print(sys.executable)"
python -m pip --versionDiagnostic habit
When an import fails or a notebook behaves strangely, check the active environment and kernel before reinstalling packages.
Export the environment with the analysis:
A collaborator can reconstruct it with:
Also record the data version, cohort definition, code commit, random seed, model artifact, and evaluation configuration.
Reproducibility
environment.yml records software dependencies. We still need a data dictionary, analysis plan, model card, and a statement about the population.
Risk
"67", 67, and 67.0 may print similarly, but they do not behave identically.
shape: (3, 4)
first row: [ 67 1 121 119]
age column: [67 54 73]
Pause and discuss
Before running: is X[:, 0] one patient, one feature, or one prediction?
[ True False True False True]
[67 73 81]
A mask has one truth value per element. Combining masks with &, |, and ~ is often clearer than a long loop.
Risk
Use parentheses around comparisons. In array code, and and or might not mean what you intend.
For a matrix X with shape (patients, features):
| Operation | Meaning |
|---|---|
X.mean(axis=0) |
one mean per feature |
X.mean(axis=1) |
one mean per patient |
X.sum() |
one total across everything |
X[:, j] |
feature j for all patients |
X[i, :] |
all features for patient i |
Pause and discuss
Which axis should be used to compute training-set means for feature standardization?
# Columns: pre-op haemoglobin (g/L), creatinine (µmol/L), bypass time (min)
X = np.array([[119., 82., 121.],
[137., 71., 88.],
[108., 104., 176.]])
feature_means = X.mean(axis=0) # one mean per clinical variable
patient_means = X.mean(axis=1) # mixes incompatible units
print("feature-centred:\n", X - feature_means)
print("patient-centred:\n", X - patient_means[:, None])feature-centred:
[[ -2.3333 -3.6667 -7.3333]
[ 15.6667 -14.6667 -40.3333]
[-13.3333 18.3333 47.6667]]
patient-centred:
[[ 11.6667 -25.3333 13.6667]
[ 38.3333 -27.6667 -10.6667]
[-21.3333 -25.3333 46.6667]]
Both operations run. Only the first resembles a sensible preprocessing step. The second averages grams per litre, micromoles per litre, and minutes—as if those units shared a scale.
Clinical meaning
Syntactically valid array arithmetic can be scientifically or logically meaningless.
import pandas as pd
df = pd.read_csv("cardiac_registry.csv")
print(df.shape)
print(df.dtypes)
print(df.head())
print(df.isna().mean().sort_values(ascending=False).head(15))
print(df["SSI_30d"].value_counts(dropna=False))Data principle
Before fitting anything, verify identifiers, duplicated rows, units, impossible values, event prevalence, temporal coverage, and the provenance of every candidate predictor.
A blank may mean:
Pause and discuss
For each important variable, write a sentence beginning: “This value is missing when…”
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
imputer = SimpleImputer(strategy="median")
scaler = StandardScaler()
X_train_i = imputer.fit_transform(X_train)
X_train_s = scaler.fit_transform(X_train_i)
X_valid_s = scaler.transform(imputer.transform(X_valid))
X_test_s = scaler.transform(imputer.transform(X_test))Risk
If test-set values influence imputation, scaling, feature selection, or representation learning, the evaluation is contaminated.
# Loop
scores = []
for row in X:
scores.append(sum(row[j] * w[j] for j in range(len(w))) + b)
# Vectorized
scores = X @ w + bVectorization can expose the mathematical structure and make shape errors easier to detect. It can also be a bit faster.
Record:
Evidence
A result that cannot be reconstructed is difficult to audit, update, or trust. (Kapoor et al. 2024; Sendak et al. 2020)
What does “containerized” mean here?
A container image packages the application, runtime, dependencies, configuration, and startup command. A container is a running instance of that image. Docker and Podman are tools that build and run container images.
For this course, containerization supports reproducible local execution.
For one patient at one index time:
\[ \mathbf{x} = [\text{age},\;\text{diabetes},\;\text{bypass time},\;\text{haemoglobin}]^\top \]
A vector has:
Risk
A categorical code such as procedure type is not automatically a meaningful coordinate just because it is stored as an integer.
\[ X = \begin{bmatrix} 67 & 1 & 121 & 119\\ 54 & 0 & 88 & 137\\ 73 & 1 & 176 & 108 \end{bmatrix} \]
Rows are patients or surgeries; columns are features.
The shape here is \(3\times4\): three examples, four features.
Pause and discuss
If w contains one weight per feature, what shape must w have for X @ w to work?
\[ z = \mathbf{x}^{\top}\mathbf{w} + b = \sum_j x_j w_j + b \]
| Feature | Value \(x_j\) | Weight \(w_j\) | Contribution \(x_jw_j\) |
|---|---|---|---|
| age (standardized) | 0.7 | 0.30 | 0.21 |
| diabetes | 1 | 0.55 | 0.55 |
| bypass time | 1.1 | 0.44 | 0.48 |
| haemoglobin | -0.6 | -0.22 | 0.13 |
Model principle
For a linear model, contributions are exact decompositions of the score. They are not automatically causal explanations.
\[ \mathbf{z}=X\mathbf{w}+b \]
X is dotted with the same w;\[ z = b + \sum_j x_jw_j \]
The intercept is the score when all encoded features are zero.
That interpretation depends on encoding:
Pause and discuss
Interpret the intercept only after you understand how every feature was encoded.
Bad encoding:
This implies that “combined” is numerically farther from 🔗 CABG than valve is.
One-hot encoding:
Data principle
Encoding choices are assumptions about geometry.
Illustrative patient vectors—not AIRO results. Euclidean distance treats one numerical unit on every feature axis as commensurate:
\[ d(\mathbf{x},\mathbf{z})=\lVert\mathbf{x}-\mathbf{z}\rVert_2. \]
Geometry is a modelling choice
The nearest neighbour changes even though the three patients do not. Scaling can be necessary, but it does not guarantee that numerical proximity is clinically meaningful.

\[ \cos(\theta)= \frac{\mathbf{x}^{\top}\mathbf{z}} {\lVert\mathbf{x}\rVert\lVert\mathbf{z}\rVert} \]
For text or representation embeddings, angle can be more useful than raw length.
Representation is not clinical equivalence
“Nearby in embedding space” does not mean clinically interchangeable, causally related, or safe to treat alike.
\[ z_{ij}=\frac{x_{ij}-\mu_j^{\text{train}}} {\sigma_j^{\text{train}}}. \]
What is preserved
Ordering and distributional shape remain the same. The transformation changes location and scale—not the underlying observations.

Standardization does not make data Gaussian
It does not remove skew, outliers, missingness, or site shift. Preserve the fitted transformer with the model and monitor future z-scores against the training distribution.
A model \(f_\theta\) maps inputs to predictions. A loss measures how undesirable those predictions are on training examples.
\[ \hat y_i=f_\theta(\mathbf{x}_i), \qquad \mathcal{L}(\theta)=\frac{1}{n}\sum_i \ell(y_i,\hat y_i) \]
\[ f'(x)=\lim_{h\to0}\frac{f(x+h)-f(x)}{h} \]
A derivative answers:
If we change this input or parameter slightly, how does the output change locally?
It does not describe the whole function, guarantee linearity, or tell us whether changing a clinical variable would cause a different outcome.
For a loss depending on many parameters:
\[ \nabla_\theta \mathcal{L}= \begin{bmatrix} \frac{\partial \mathcal{L}}{\partial \theta_1}\\ \vdots\\ \frac{\partial \mathcal{L}}{\partial \theta_p} \end{bmatrix} \]
The gradient points in the direction of steepest local increase. To reduce the loss, move in the opposite direction.

Consider the constant predictor:
\[ \mathcal{L}(\mu)=\frac{1}{n}\sum_i(y_i-\mu)^2 \]
\[ \mu_{t+1}=\mu_t- \eta\frac{d\mathcal{L}}{d\mu}. \]
If \(y=f(u)\) and \(u=g(x)\), then
\[ \frac{dy}{dx}=\frac{dy}{du}\frac{du}{dx}. \]
Neural-network backpropagation repeatedly applies this rule through a graph of operations.
\[ \sigma(z)=\frac{1}{1+e^{-z}} \]

A sigmoid output is not automatically a well-calibrated probability; calibration is an empirical property of model predictions in a population.
Let \(A=\) observed SSI and \(B=\) model alert. A probability is the share of the reference population that falls in a region.
Read the conditional in words
\(P(A\mid B)\) asks: among the cases in \(B\), what fraction are also in \(A\)?
Suppose 1,000 surgeries have:
Then:
\[ P(\text{SSI}\mid\text{alert})=\frac{40}{40+95}\approx 30\%. \]
Evidence
Even a useful test can produce many false alerts when the outcome is uncommon. Always remember your priors!
n = 1000
prevalence = 0.05
sensitivity = 0.80
specificity = 0.90
y = np.zeros(n, dtype=int)
y[:int(n*prevalence)] = 1
pred = np.zeros(n, dtype=int)
pred[:int(n*prevalence*sensitivity)] = 1
start_neg = int(n*prevalence)
pred[start_neg:start_neg+int((n-start_neg)*(1-specificity))] = 1
# shuffle only for display
rng = np.random.default_rng(5401)
idx = rng.permutation(n)
y, pred = y[idx], pred[idx]
color_code = np.where((y==1)&(pred==1), 3,
np.where((y==0)&(pred==1), 2,
np.where((y==1)&(pred==0), 1, 0))).reshape(20,50)
from matplotlib.colors import ListedColormap, BoundaryNorm
from matplotlib.patches import Patch
# colour_code:
# 0 = true negative, 1 = false negative,
# 2 = false positive, 3 = true positive
TN_COLOUR = "#d9dee7"
tile_colours = [
TN_COLOUR, # true negative: no SSI, no alert
RISK, # false negative: SSI, no alert
DAL_GOLD, # false positive: no SSI, alert
CLINICAL, # true positive: SSI, alert
]
cmap = ListedColormap(tile_colours)
norm = BoundaryNorm(np.arange(-0.5, 4.5, 1), cmap.N)
fig, ax = plt.subplots(figsize=(10, 4.5))
ax.imshow(
color_code,
cmap=cmap,
norm=norm,
aspect="auto",
interpolation="nearest",
)
ax.set_xticks([])
ax.set_yticks([])
ax.set_title(
"1,000 surgeries: true and false alerts under a 5% base rate"
)
# Recalculate the legend counts from the displayed patient tiles.
tn, fn, fp, tp = np.bincount(
color_code.ravel(),
minlength=4,
)
legend_handles = [
Patch(
facecolor=CLINICAL,
edgecolor="white",
label=f"True positive: SSI + alert (n={tp})",
),
Patch(
facecolor=DAL_GOLD,
edgecolor="white",
label=f"False positive: no SSI + alert (n={fp})",
),
Patch(
facecolor=RISK,
edgecolor="white",
label=f"False negative: SSI + no alert (n={fn})",
),
Patch(
facecolor=TN_COLOUR,
edgecolor="#9aa4b2",
label=f"True negative: no SSI + no alert (n={tn})",
),
]
ax.legend(
handles=legend_handles,
loc="upper center",
bbox_to_anchor=(0.5, -0.05),
ncol=2,
frameon=False,
fontsize=10,
handlelength=1.5,
columnspacing=2.2,
)
fig.subplots_adjust(bottom=0.25)
plt.show()For binary SSI outcome \(Y\in\{0,1\}\):
\[ E[Y]=P(Y=1), \qquad Var(Y)=p(1-p). \]
For a predicted risk \(\hat p\), the expected number of infections in a group is approximately \(\sum_i \hat p_i\) only when predictions are calibrated for that group.

Don’t jump to conclusions
A long tail may reflect patients, different care pathways, complications, transfer practices, missingness, or data error. Investigate before interpreting or deleting outliers.
Both wards average 8 days, but Ward B has a much longer tail.
That difference can change:
Report distributions, quantiles, and tail probabilities—not only averages.
rng = np.random.default_rng(5401)
los_days = rng.gamma(shape=2.2, scale=3.0, size=80)
boot_means = np.array([
rng.choice(los_days, size=len(los_days), replace=True).mean()
for _ in range(2000)
])
lo, hi = np.quantile(boot_means, [0.025, 0.975])
print({
"sample_mean_days": round(los_days.mean(), 2),
"bootstrap_95%": (round(lo, 2), round(hi, 2)),
})
fig, ax = plt.subplots(figsize=(7, 3.4))
ax.hist(boot_means, bins=35)
ax.axvline(lo, linestyle="--")
ax.axvline(hi, linestyle="--")
ax.set_xlabel("bootstrap mean postoperative length of stay (days)")
plt.show(){'sample_mean_days': np.float64(5.97), 'bootstrap_95%': (np.float64(5.19), np.float64(6.75))}
The interval reflects resampling uncertainty under assumptions about how the observed surgeries represent the target population. It does not capture unmeasured site shift, label error, or a changed care pathway.
Before a sophisticated model, compare against:
Evidence
A complex model that barely beats a strong baseline may not justify added maintenance, opacity, latency, or governance burden.
flowchart LR A[Raw cohort] --> B[Split by patient/time/site] B --> C[Fit preprocessing on train] C --> D[Fit model on train] D --> E[Tune on validation] E --> F[One final test evaluation]
flowchart LR X[All data] --> Y[Impute/scale/select on everything] Y --> Z[Random row split] Z --> Q[Repeatedly inspect test score] Q --> R[Optimistic claim]
Risk
Leakage is information crossing a boundary that the claimed deployment would not allow. (Kapoor and Narayanan 2023; Varoquaux and Cheplygina 2022)
The pneumonia risk model of Caruana et al. (2015) appeared to learn that asthma lowered pneumonia mortality risk.

Cropped from Caruana et al. (2015), Fig. 1.
What the model had learned was a channel effect: patients with asthma and pneumonia were more likely to be treated vigorously, including ICU admission.
So the model captured part of the care pathway, not just the disease process.
Note
To a large extent, realizing this is an early victory of ‘explainable AI’.
A model can predict SSI using features that are:
Good prediction tends to minimize a loss, on training data.
Causal inference asks “what would change under an intervention?”
Research models and widely deployed proprietary models have shown why external validation matters. (Futoma, Hariharan, and Heller 2017; Wong et al. 2021)
Readmission risk can support follow-up planning, but it is influenced by:
Pause and discuss
What action is available for a high-risk patient, and who has capacity to provide it?
In
[population and setting], using information available by[index time], model[name/version]estimated[outcome/horizon]. On[evaluation design], it achieved[discrimination, calibration, and uncertainty]. Performance was[subgroup/temporal summary]. At threshold[t], the expected workload and errors were[counts/rates]. These results support[narrow claim], but do not establish[causal or deployment claim].
Pause and discuss
A strong evidence statement is modest, specific, reproducible, and tied to a decision.
You are ready to continue when you can:
A table contains one row per surgery_id, but you discover:
| Finding | Example |
|---|---|
| duplicated patients | two surgeries from the same person split across train and test |
| mixed units | creatinine values around 1.1 and 98 in one column |
| late information | “discharge antibiotics” used for an ICU-transfer model |
| informative missingness | lactate measured mainly when clinicians are worried |
| join expansion | one surgery becomes eight rows after joining notes |
| era marker | a form field appears only after a registry redesign |
For each finding, how do we: detect, repair, document, and estimate risk.
Which problems are coding errors, which are study-design errors, and which are properties of care?

DGIN 5401 · Operationalized ML in Healthcare