Time Zero (Index Date) Alignment
The design rule that fixes a single date per patient at which eligibility is met, treatment strategy is assigned, baseline covariates stop being measured, and follow-up and outcome risk begin—so that classification, person-time, and confounder measurement are synchronized exactly as they would be at randomization in the trial being emulated.
In plain language
Time-zero alignment is the rule that picks one single date per patient where the clock starts: the day they qualify for the study, the day we decide which treatment group they are in, and the day we begin counting their follow-up and watching for outcomes. It answers the question, "From exactly when should we start measuring this patient, so we are comparing the two groups fairly the way a coin-flip trial would?" Get this date wrong and you can hand one group free, event-free time it never actually earned, which fakes a benefit that isn't real. The catch: you may only use information you'd know on or before that day to sort a patient into a group.
Time zero
(the index date, t0, or cohort-entry date) is the instant at which a patient simultaneously (1) satisfies all eligibility criteria, (2) is assigned to a treatment strategy, (3) has baseline covariates fixed (everything measurable up to and including t0, nothing after), and (4) begins to accrue person-time and outcome risk. In a randomized trial these four events coincide by construction at randomization. In observational data they do not coincide unless the analyst forces them to, and the entire validity of a longitudinal RWE study rests on doing so. The discipline of aligning eligibility, assignment, and follow-up onset at one date is the operational heart of target-trial emulation; getting it wrong is the single most common source of catastrophic, non-fixable bias in pharmacoepidemiology.
Core conceptual distinction
The defining requirement is that exposure classification must be determinable using only information available at or before t0. The failure mode is using future information to define cohort entry or arm assignment, which guarantees that the classified-exposed group must survive (and remain event-free) long enough to receive exposure—immortal time bias. The classic example: define "statin user" as anyone who fills a statin during follow-up, but start the clock at hospital discharge. Patients who die in week one cannot become "users," so the user group banks event-free person-time it never earned. Three distinct biases all trace to t0 misalignment and must be separated: (1) immortal time—follow-up starts before the moment exposure is determined; (2) prevalent-user / left-truncation bias—t0 is set at an arbitrary calendar date among ongoing users, conditioning on survival and depleting susceptibles; (3) differential alignment by arm—the two strategies' t0 dates correspond to different points in the disease course (e.g., the new drug is reached only after the comparator fails), embedding confounding by indication into the time axis itself. The estimand you can recover is governed by what t0 represents: if t0 = initiation, the contrast is an initiation (ITT-like) effect; if a grace period is allowed for a strategy to be "started," eligibility-time ambiguity arises and the per-protocol estimand requires cloning/censoring/weighting rather than a single assigned t0.
Pros, cons, and trade-offs
- vs an "ever-exposed" / time-fixed classification with follow-up from diagnosis: Aligning t0 at the exposure decision eliminates immortal time entirely; the ever-exposed approach is not a milder version of the same design, it is a different and biased estimand. There is no analytic patch (no covariate adjustment, no PS) that repairs misclassified person-time. Always prefer aligned t0; the only legitimate alternative when exposure genuinely accrues after a fixed eligibility event is to model exposure as time-varying (landmark analysis or a time-dependent Cox/pooled-logistic model), which is a deliberate analytic choice, not a default. - vs prevalent-user (current-user at a calendar date) designs: A new-user t0 at first qualifying fill removes left-truncation, depletion-of-susceptibles, and adjustment for post-initiation mediators. Cost: smaller cohorts and a population restricted to initiators, who can differ from the prevalent users who dominate practice. Prefer aligned new-user t0; reach for a prevalent new-user (Suissa) design only when incident initiation is too rare to study. - vs single-decision t0 with clone-censor-weight (CCW): When a strategy is defined by a sustained or dynamic regimen, or when a grace period makes "the" assignment date ambiguous (patients who could start within X days all qualify for both strategies at baseline), a single hard t0 forces an artificial choice and reintroduces selection. CCW assigns every eligible person to all compatible strategies at t0, then censors and weights as their data diverge. Cost: substantially more complex to specify, fit, and defend. Prefer a single aligned t0 for clean two-drug initiation contrasts; escalate to CCW only when the protocol genuinely requires a dynamic per-protocol estimand.
When to use
— every longitudinal RWE study that produces a comparative or absolute time-to-event, person-time, utilization, or cost estimate—comparative effectiveness, drug/vaccine safety, screening or procedure effects, and any target-trial emulation. Pre-specifying t0 (and the rule that maps each patient to it) is a non-negotiable protocol element; the diagnostics that prove it worked—distribution of t0 by arm, attrition at each eligibility rule, 5–10 patient-level timelines per arm, and ±30/90-day sensitivity on washout/grace/lookback—belong in the SAP.
When NOT to use — and when it is actively misleading or dangerous
- Do not set a single fixed t0 when exposure is intrinsically post-baseline and time-varying (e.g., effect of achieving a lab target, of cumulative dose, or of adherence measured over follow-up). Forcing these into a baseline classification creates immortal time. Use landmark or time-dependent models instead. - Do not anchor t0 on a downstream event (first hospitalization for the outcome's complication, second prescription, "confirmed responder"). Conditioning cohort entry on a future event selects on survival and on the outcome pathway. - Do not let the two arms' t0 represent different clinical moments. If the comparator's t0 is first-line initiation but the study drug's t0 is post-failure switch, the time axes are not exchangeable; balance tables will look fine yet the estimate is confounded by indication baked into t0. This is the most dangerous case because it is invisible to standardized differences computed at each arm's own baseline. - Do not assign t0 from a procedure that itself defines survival (e.g., transplant, complex surgery) without a g-method or sequential-trial design; "time to procedure" is immortal by construction.
Data-source operational depth
- Administrative claims (FFS vs Medicare Advantage): t0 is typically the `fill_date` of the first qualifying NDC or the service date of a qualifying procedure. The lethal trap is MA-only person-time: capitated Medicare Advantage plans do not generate complete FFS encounter/pharmacy claims, so a clean washout before t0 can be missingness masquerading as incidence, and competing events (death) are unevenly captured. Require continuous A/B/D (or commercial medical+pharmacy) enrollment spanning the entire washout through t0, and exclude MA-only spans rather than treating them as drug-free. Reversed/rebilled and same-day mail-order + retail fills can split or duplicate the index event—dedupe before taking the minimum date. In elderly cohorts, differential competing risk of death by arm distorts cause-specific vs cumulative- incidence interpretation, which must be reconciled with the estimand. - EHR: The exposure decision is the order or administration, not a dispensing; an order without a confirmed fill is an intention, not an initiation, so linkage to pharmacy claims sharpens t0. Visit-driven capture means baseline covariates are observed only when a patient happens to have an encounter, so the "up-to-t0" window can be sparse and informatively missing; a patient who leaves the system contributes biased follow-up. Define the observation window explicitly and treat loss to follow-up as potentially informative. - Registry: Strong for the qualifying clinical event (diagnosis date, stage, adjudicated index procedure) that often is t0, but typically blind to subsequent pharmacy exposure and to deaths occurring outside the registry. Link to claims for fill history and to a death index to firm up censoring; verify the registry's enrollment/adjudication date is not itself a post-hoc, outcome-informed date. - Linked claims–EHR–vital records: The ideal substrate (EHR severity + claims completeness + reliable mortality) but order date, fill date, and service date frequently disagree by days to weeks; pre-specify which date defines t0 and reconcile the others before assignment, because a few days' slippage changes who is "incident" and whether a baseline covariate falls before or after t0.
Worked claims example (immortal time made concrete)
Question: 1-year all-cause mortality after acute MI in patients who initiate a beta-blocker vs those who do not, in a commercial + Medicare FFS database. Naive (wrong) analysis: t0 = MI discharge date for everyone; classify "beta-blocker user" = any beta-blocker `fill_date` in the 90 days after discharge. Result: the user arm is artificially protected, because anyone who dies in days 1–89 before filling is forced into the non-user arm—the days from discharge to first fill are immortal (event-free by definition) and wrongly credited to users. Aligned analysis: require ≥365 days continuous A/B/D enrollment with no beta-blocker fill in the 365-day lookback (washout, so initiation is incident, MA-only spans excluded). Set t0 = first post-discharge beta-blocker `fill_date` for initiators; for the comparator, use a sequential/landmark device—e.g., assign each non-filling-survivor a matched t0 at the same number of days post-discharge (or build a sequence of nested trials by day), so both arms start the clock at the same elapsed time and the immortal interval is removed from both. Measure baseline covariates only in `[t0 − 365, t0]` (prior comorbidities, utilization, infarct severity proxies), fit a PS, and follow from t0 to death, censoring at disenrollment, end of data, and (for an as-treated estimand) discontinuation = last `days_supply` end + a 30-day grace period or switch. Report the t0 distribution by arm and rerun with the washout and grace period at ±30/90 days. The aligned design typically shrinks the spurious "beta-blocker" benefit by a large margin—the textbook signature of corrected immortal time.
Worked example
Scenario
One patient is discharged from the hospital after a heart attack on 2024-01-01 and we want to study whether starting a beta-blocker lowers 1-year death. They fill their first beta-blocker on 2024-03-01. We compare a wrong way to start the clock (at discharge, but label them a "user" using a fill that happens later) against the right way (start the clock on the fill date). The window we can observe ends 2024-08-28.
Dataset
The raw rows an analyst would see: one discharge record, one enrollment span, and one pharmacy fill.
| person_id | event_type | date | drug | days_supply |
|---|---|---|---|---|
| 2001 | hospital_discharge | 2024-01-01 | ||
| 2001 | enrollment_span_start | 2023-01-01 | ||
| 2001 | pharmacy_fill | 2024-03-01 | beta_blocker | 90 |
Steps
Wrong way: start the clock at discharge (2024-01-01) but call the patient a "beta-blocker user" because of a fill that only happens on 2024-03-01.
From 2024-01-01 to 2024-03-01 is 60 days (Jan has 31, Feb 2024 has 29). The patient had to survive all 60 of those days just to live long enough to fill the drug.
Counting those 60 days as user follow-up means user follow-up runs 2024-01-01 to 2024-08-28 = 240 days, and those first 60 days are guaranteed event-free — that is the immortal stretch.
Right way: set time zero at the fill date (2024-03-01) and only count days after that. Follow-up runs 2024-03-01 to 2024-08-28 = 180 days.
The difference, 240 − 180 = 60 days, is exactly the immortal time the wrong design wrongly handed to the user group.
Result
Misaligned follow-up = 240 days vs aligned follow-up = 180 days; the misaligned design credits 60 extra event-free (immortal) days (2024-01-01 to 2024-03-01) to the beta-blocker group that aligning time zero at the 2024-03-01 fill removes.
Timeline Spec
- Title
Immortal time from a misaligned time zero, and how aligning t0 at the first fill removes it (one post-MI patient)
- Window
- Start
2024-01-01
- End
2024-08-28
- Label
Observation window: 2024-01-01 discharge to 2024-08-28 data end
- Events
- Label
Hospital discharge (eligibility event)
- Start
2024-01-01
- Quantity
day 0 of the wrong clock
- Label
First beta-blocker fill = correct time zero
- Start
2024-03-01
- Length Days
90
- Quantity
90 days_supply
- Spans
- Kind
unexposed
- Start
2024-01-01
- End
2024-02-29
- Label
60-day immortal stretch wrongly credited to the user group
- Kind
followup
- Start
2024-03-01
- End
2024-08-28
- Label
180 aligned follow-up days (clock starts at the fill)
- Kind
washout
- Start
2023-01-01
- End
2023-12-31
- Label
365-day washout: no prior beta-blocker before t0
- Result
- Label
Misaligned 240 days − aligned 180 days = 60 immortal days removed by aligning t0
- Value
60
- Caption
Top: starting the clock at discharge (2024-01-01) while labeling the patient a user from a 2024-03-01 fill banks a 60-day immortal stretch the patient had to survive to fill the drug. Bottom: setting time zero at the 2024-03-01 fill counts only the 180 real follow-up days, removing the immortal time.
- Alt Text
Timeline for one post-heart-attack patient showing a 60-day immortal interval between the 2024-01-01 discharge and the 2024-03-01 first beta-blocker fill, a 365-day washout before time zero, and 180 days of aligned follow-up that begins at the fill date.
Runnable example
python implementation
Time-zero assignment from claims-style inputs, with an explicit immortal-time guard. Required inputs (already cleaned and de-duplicated): rx : pharmacy fills -> person_id, fill_date (datetime64), drug_class in {'STUDY','COMPARATOR'}, days_supply (int)...
import pandas as pd
WASHOUT_DAYS = 365 # drug-free + continuous-enrollment lookback that defines an incident (new) user
def assign_time_zero(rx: pd.DataFrame, enroll: pd.DataFrame) -> pd.DataFrame:
rx = rx.sort_values(["person_id", "fill_date"])
study = rx[rx["drug_class"].isin(["STUDY", "COMPARATOR"])].copy()
# De-duplicate same-day/rebilled fills so the index event is a single row, then take the EARLIEST qualifying fill.
study = study.drop_duplicates(subset=["person_id", "fill_date", "drug_class"])
idx = (study.groupby("person_id", as_index=False)
.first()
.rename(columns={"fill_date": "index_date", "drug_class": "arm"}))
# Immortal-time guard: t0 (= the first qualifying fill) is by construction the classification date.
# Assert no fill used for arm assignment occurs AFTER the index date -> classification uses only info <= t0.
chk = study.merge(idx[["person_id", "index_date"]], on="person_id")
offending = chk[chk["fill_date"] < chk.groupby("person_id")["fill_date"].transform("min")]
assert offending.empty, "arm assignment must use only the earliest fill (no post-t0 information)"
# New-user: no study/comparator fill in the washout window strictly before t0.
prior = study.merge(idx[["person_id", "index_date"]], on="person_id")
in_washout = prior[(prior["fill_date"] < prior["index_date"]) &
(prior["fill_date"] >= prior["index_date"] - pd.Timedelta(days=WASHOUT_DAYS))]
idx = idx[~idx["person_id"].isin(in_washout["person_id"])].copy()
# Continuous, FFS-observable enrollment spanning the full washout through t0 (no MA-only gaps).
e = enroll.merge(idx[["person_id", "index_date"]], on="person_id")
e["covers"] = ((e["enroll_start"] <= e["index_date"] - pd.Timedelta(days=WASHOUT_DAYS)) &
(e["enroll_end"] >= e["index_date"]) & (~e["ma_only"]))
eligible = e.loc[e["covers"], "person_id"].unique()
cohort = idx[idx["person_id"].isin(eligible)].copy()
cohort["baseline_start"] = cohort["index_date"] - pd.Timedelta(days=WASHOUT_DAYS) # covariate window opens here
return cohort[["person_id", "arm", "index_date", "baseline_start"]]r implementation
Time-zero assignment with data.table; inputs mirror the Python version. rx : person_id, fill_date (Date), drug_class in {'STUDY','COMPARATOR'}, days_supply (int) enroll : person_id, enroll_start, enroll_end (Date), ma_only (logical) Stops with an error if...
library(data.table)
WASHOUT_DAYS <- 365L
assign_time_zero <- function(rx, enroll) {
setDT(rx); setDT(enroll)
study <- rx[drug_class %chin% c("STUDY", "COMPARATOR")]
# Drop same-day / rebilled duplicates, then take the EARLIEST qualifying fill as t0 and arm.
study <- unique(study, by = c("person_id", "fill_date", "drug_class"))
setorder(study, person_id, fill_date)
idx <- study[, .(index_date = fill_date[1L], arm = drug_class[1L]), by = person_id]
# Immortal-time guard: classification must rest on the first fill only.
stopifnot(nrow(study[fill_date < study[, min(fill_date), by = person_id][idx, on = "person_id", V1]]) == 0L)
# New-user: no study/comparator fill in the washout window strictly before t0.
study2 <- merge(study, idx[, .(person_id, index_date)], by = "person_id")
prior_ids <- unique(study2[fill_date < index_date &
fill_date >= index_date - WASHOUT_DAYS, person_id])
idx <- idx[!person_id %chin% prior_ids]
# Continuous, FFS-observable enrollment across the full washout through t0 (no MA-only spans).
e <- merge(enroll, idx[, .(person_id, index_date)], by = "person_id")
ok <- e[enroll_start <= index_date - WASHOUT_DAYS &
enroll_end >= index_date & !ma_only, unique(person_id)]
cohort <- idx[person_id %chin% ok]
cohort[, baseline_start := index_date - WASHOUT_DAYS]
cohort[, .(person_id, arm, index_date, baseline_start)]
}