Survivorship Bias
The table contains the customers, companies or machines that are still here. The ones that failed were deleted, archived or never joined, and the model learns what survivors look like.
The problem, the obvious approach, and why it breaks
Every lesson starts where the work starts: someone has a problem, and the first model that comes to mind looks fine offline.
Who is missing from the table because they did not make it, and is the model being asked to predict the very thing that removed them?
An industrial-equipment company wants to predict which pumps will fail in the next 90 days from their sensor history. The fleet database has telemetry for every pump currently installed.
Query the fleet database for all pumps and their sensor history, build pump-month rows, label from work-orders. The database has every pump, so the dataset has every pump.
The pumps most worth predicting — the ones that fail catastrophically and are scrapped — are exactly the ones archived out. The model learns from survivors, whose failures are mild, and never sees the signature of the failures that cost the most.
- The pumps most worth predicting — the ones that fail catastrophically and are scrapped — are exactly the ones archived out. The model learns from survivors, whose failures are mild, and never sees the signature of the failures that cost the most.
- Validation looks good, because the held-out set is also survivors. In production, a pump on the way to a catastrophic failure shows sensor patterns the model has never seen and scores as healthy.
- A feature like
age_in_servicebecomes protective — old pumps in the table are the ones that never broke — and the model deprioritises inspecting the oldest machines.
What is being predicted, and from what data
This domain leads with these two. A target nobody defined precisely is a label nobody can trust, and a dataset nobody can describe is a model nobody can debug.
- Predict a failure event within 90 days of a snapshot, from sensor aggregates up to the snapshot. The label is a recorded failure work-order.
- The decision is which pumps to inspect this month, from a maintenance budget that covers perhaps a tenth of the fleet.
- One example is one pump-month: sensor aggregates for the month before the snapshot, and whether a failure work-order was opened in the 90 days after.
- The fleet database is a current-state system: when a pump is scrapped after a catastrophic failure, its record and telemetry are archived out of the main tables.
- Pumps that failed early in life are therefore absent; pumps that have run for years are over-represented, and their failures are the gentle, repairable kind.
How it actually works
Precisely enough to predict its behaviour — not a framework API.
- Survivorship bias is selection bias where the selecting process is the outcome itself. A row exists because the entity did not experience the event, or experienced only the mild version of it; the severe version removed it from the data.
- The effect on features is systematic: anything correlated with failure looks benign, because the entities that had it and failed are gone. Age, load, vibration history all shift toward "harmless" in the surviving population.
- Because the table's completeness is judged against itself — it contains every pump *in the table* — no data-quality check catches it. The missing rows were never late or null; they were deleted or never written.
The table is a census of survivors
A current-state fleet database answers "what is installed now". That is a fine question for operations and the wrong one for a failure model, because the population it describes was produced by the event being predicted. The pumps that failed badly are the ones that are not there.
The compare below is the same query against two sources. The right-hand side is more work and has worse features. It is also the only one that contains the failures that matter.
SELECT pump_id FROM fleet.pumps WHERE status = 'installed' — every pump that is still running, with full telemetry. Scrapped pumps were archived out on decommission.
SELECT pump_id, installed_at FROM procurement.installations — every pump that ever entered service, joined to telemetry from the archive for those no longer in the fleet, with a flag for how they left.
The population must be defined before the outcome acted on it. The installation log recorded each pump on entry, so it cannot have been filtered by failure; the fleet table is filtered by failure by construction.
How a protective feature is born
Among survivors, the oldest pumps are the ones that never had a serious problem. Their vibration history is calm because calm pumps survive. So in the training set, high age and calm history predict "no failure", and the model learns to leave old, quiet pumps alone.
That is not a spurious correlation in the training data; it is a true fact about survivors. It is a wrong fact about the fleet, and the model has no way to know which population it is describing.
looks like A plausible, well-defined feature: months since installation. Strongly negatively associated with the failure label in the training set.
why it leaks It does not leak the label; it leaks the selection. Pumps only reach high age by not failing, and the ones that failed young were removed, so the feature's association with the label is manufactured by which rows exist.
fix Define the population from the installation log and recover the archived pumps with their history, so age is learned across every pump that ever reached each age, not only those that survived past it.
Sizing the blind spot when it cannot be closed
Sometimes the archived telemetry is genuinely gone. The population can still be reconstructed as counts: from procurement, how many pumps were installed per quarter, and from the fleet table, how many of them are still present at each age. The gap between the two curves is the share of the fleet the model has never seen.
That gap does not make the model better. It makes the metric honest, and it tells the maintenance team which pumps the model's score should not be trusted for.
Every entity that could have experienced the outcome is in the training population, with its history up to the point it left, whether or not it survived.
holds when The population is drawn from an append-only entry record and exits are recorded rather than deleted; the cohort attrition curve from procurement matches the training set's coverage.
breaks when A retention policy purges archived entities; asset ids are reused on replacement; a new source system starts as current-state only.
respond Recover or reconstruct the missing entities before retraining; if impossible, restrict the model's claims to the cohorts it has seen and route the rest to rule-based inspection.
1from collections import defaultdict2 3# installed: {pump_id: installed_quarter} from procurement, append-only4# present: set of pump_ids currently in the fleet table5by_cohort = defaultdict(lambda: [0, 0])6for pump, q in installed.items():7 by_cohort[q][0] += 18 by_cohort[q][1] += pump in present9 10for q in sorted(by_cohort):11 n, alive = by_cohort[q]12 missing = 1 - alive / n13 print(f"{q}: installed={n} present={alive} unseen_by_model={missing:.0%}")14 15# The unseen share for older cohorts is the population whose failures16# the training set cannot contain. Report it beside every metric.Nothing here needs telemetry. Two lists of ids — who arrived, who is still here — are enough to state how much of the population the offline metric is silent about.
How to build it
Most important first.
- Rebuild the population from an append-only source: the installation log, the procurement record, the event log — anything that recorded an entity when it arrived rather than only while it survives (Keeping Raw History: The Recovery Position and the Liability, The Event Log).
- Reconstruct the archived entities and their telemetry from cold storage, even partially; a few hundred catastrophic failures with sensor history are worth more than a million survivor-months.
- Where the removed entities cannot be recovered, label their absence: build a cohort by installation date and count how many are missing at each age, so the model's blind spot is at least sized (Selection Bias).
- Treat current-state tables as unfit for training population definition; use them for features only when joined to a population defined elsewhere (Snapshot Tables).
- Monitor the gap between the age distribution in the training set and in the fleet at installation time; survivorship shows up as a training set that is older than the fleet ever was.
What to measure
Which number actually maps to the decision — and which numbers look relevant and are not.
- The cohort attrition curve: of pumps installed in each quarter, what share is still in the table at each age. The area above that curve is the population the model has never seen.
- The severity mix of failures in the training labels against the severity mix in incident records or warranty claims, which are kept even when the pump is not.
- The offline failure-prediction metric is a statement about mild failures among survivors, and should be labelled as such wherever it is reported.
What must stay true after deployment
The field this whole domain exists for. A model is a set of assumptions with weights attached; these are the ones a monitor or a test should be checking.
- The training population was defined from a source that recorded every entity at entry, so entities removed by the outcome are present with their pre-removal history.
- The severity distribution of failures in the training labels matches the severity distribution of failures the maintenance team actually faces.
- Entity identifiers are not reused across physical replacements, or replacements are detectable so a swap is not read as survival.
- Offline: compare the number of pumps ever installed, from procurement, against the number of distinct pumps in the training population; the difference is the survivorship gap and should be near zero.
- Online: when a catastrophic failure occurs, check what the model scored that pump in the preceding months; a pattern of confident "healthy" scores before severe failures is the diagnostic signature.
- Over time: recompute the cohort attrition curve quarterly and the failure-severity mix of new labels against incident records.
What can go wrong
- The archived telemetry is recovered, but it was stored at a coarser resolution, so the recovered pumps have systematically different features and the model learns to identify "recovered from archive" as the failure signal.
- The installation log is used for the population, but pumps that failed within the warranty period were replaced under the same asset id, so the failure is invisible as an entity swap.
- The cohort attrition curve is computed once; a new pump model with a different failure profile enters the fleet and the blind spot changes shape.
- Recovering archived entities means a data-engineering project against cold storage and old schemas, with imperfect results, before any modelling starts.
- Defining the population from an append-only log usually means fewer usable features for the recovered entities, so the model is trained on a lowest-common-denominator feature set.
- Sizing the blind spot without closing it gives an honest confidence bound but no better model; the business may prefer a confident wrong number.
- "Old pumps rarely fail, so age is protective." Old pumps *in the table* rarely fail, because the ones that did are no longer in the table. The feature is measuring the selection, not the physics.
- "The data-quality checks are all green, so the table is complete." Completeness was measured against the table's own expectations. The missing entities never generated a null, a late row or a schema error.
- "We only have a few catastrophic failures, so they are not worth recovering." They are the events the decision is about. A training set with a million survivor-months and no catastrophic failures cannot learn the thing the inspection budget exists for.
Where this applies
ML advice is stated as universal far more often than it is. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALWhenever the outcome removes entities from the system that records them — churned customers, failed companies, scrapped machines, dead patients in a registry of living ones — the surviving table carries this bias regardless of task or model.
- DATA-SPECIFICSevere in current-state operational databases that archive or delete on exit; mild or absent in append-only event logs and warehouses that keep history, which is the reason to define populations from those instead.
Where the depth lives
This domain teaches the model and hands the rest off by name.