Persistence Time to Discontinuation
The duration a patient continues an initiated therapy — from the index fill until a permissible-gap rule declares discontinuation — analyzed as a time-to-event endpoint distinct from day-to-day adherence (implementation) intensity.
On this page
Persistence measures how long a patient keeps refilling a medication before stopping — specifically, the number of days from their first fill until the gap between when one supply ran out and the next fill arrived grew longer than an allowed limit (the permissible gap, commonly 30, 60, or 90 days). Unlike PDC, which asks how much medication a patient had on hand during a fixed window, persistence asks a simpler duration question: how many days did this person stay on therapy before quitting? One honest caveat: persistence built from pharmacy claims cannot see medications dispensed inside a hospital, free samples, or cash-paid fills, so those unobserved supply days may make a patient look like they stopped when they did not.
Persistence
is the duration phase of medication-taking behavior: how long a patient stays on a therapy after initiating it, ending when an allowable gap between the exhaustion of one fill's `days_supply` and the next fill exceeds a pre-specified threshold (the permissible gap, e.g., 30, 60, or 90 days). The day after the gap rule is breached becomes the discontinuation date; the interval from `index_date` to that date is the persistence time. Because every patient either discontinues, is censored (death, disenrollment, end of data), or remains persistent at study end, persistence is naturally a time-to-event outcome — not a ratio.
The standardization literature (Cramer 2008, Vrijens ABC taxonomy 2012) is emphatic that this is a separate construct from adherence/implementation (PDC, MPR), which measures dose-taking intensity while the patient is still on therapy. A patient can be highly persistent (no qualifying gap for two years) yet poorly adherent (PDC of 0.55 from chronically late refills), and vice versa.
Core estimand distinction
. Persistence has two estimand layers that must be pre-specified, and conflating them is the most common error in the literature. (1) What event defines discontinuation — a permissible-gap breach on the index agent only (pure persistence), or a gap that is not bridged by a switch/add-on (treatment-episode persistence).
(2) How competing events are handled — death and, in many designs, switching are competing risks that prevent the patient from ever being observed to discontinue the index drug. The naive Kaplan–Meier "persistence curve" treats death/disenrollment as non-informative censoring and therefore overstates the probability of remaining persistent when competing events are common (e.g., elderly oncology or heart-failure cohorts).
The honest choices are the cause-specific hazard (rate of discontinuation among those still at risk and alive — answers an etiologic "why do people stop" question) versus the subdistribution hazard / cumulative incidence function (Fine–Gray — answers the prognostic "what fraction will be off drug by month 12" question, keeping those who died in the risk set). These are different numbers with different interpretations; report the CIF, not 1−KM, whenever competing risks are non-trivial.
Pros, cons, and trade-offs
.
- vs pdc-proportion-of-days-covered (and mpr-medication-possession-ratio): Persistence captures time on therapy — the quantity that drives cumulative exposure, lines-of-therapy denominators, and the "on-treatment" windows for outcome assessment — whereas PDC/MPR capture intensity of supply over a fixed denominator and silently assume the patient is still on drug. Cost: persistence is acutely sensitive to the chosen gap length (Gardarsdottir 2010 shows episode counts and discontinuation rates swing materially from 30 to 90 days) and says nothing about adherence quality during the persistent period. Prefer persistence when the question is continuation/discontinuation, episode length, or defining on-treatment exposure; prefer PDC when the question is degree of adherence within a fixed window.
- vs a binary "persistent at 12 months" proportion: The time-to-event formulation uses all the follow-up, respects differential censoring, and supports comparative hazard ratios; the binary endpoint discards timing and mishandles patients censored before the landmark. Prefer the survival formulation for almost all analytic work; reserve the binary version for simple descriptive reporting at a single, fully-observed landmark.
- vs treatment-switch / switch-add-on-augmentation-rwe: Persistence logic supplies the gap machinery that switch and augmentation algorithms depend on, but the gap definition directly shapes switch classification — a short permissible gap inflates apparent discontinuations and restarts. Use persistence as the substrate, then layer switch rules on top with a consistent gap.
When NOT to use — and when it is actively misleading or dangerous
.
- No reliable dispensing/`days_supply` data. EHR prescription orders are not fills; persistence built on orders systematically overstates continuation because it cannot see primary non-adherence (an order never filled — see primary-non-adherence-initiation) or true discontinuation. Use linked pharmacy claims or treat order-based persistence as a different, weaker construct.
- Competing risks ignored in a high-mortality cohort. Reporting 1−KM as "persistence" when death is frequent is actively misleading: it inflates apparent persistence and, if mortality differs by arm, biases the comparison. Switch to CIF/Fine–Gray or report cause-specific hazards with explicit handling of the competing event.
- MA-only or capitated person-time. Persistence computed where pharmacy claims are not fully captured manufactures spurious gaps (a "discontinuation" that is really unobserved fills). Restrict to person-time with complete pharmacy benefit.
- The real question is adherence intensity, not duration. A patient who fills sporadically but never opens a gap-length gap is "persistent"; if you care about whether they took enough drug, use PDC/MPR, not persistence.
- Immortal-time in procedure/initiation framing. If follow-up or persistence start is set at a landmark before the index fill (e.g., diagnosis date), the pre-fill interval is immortal and biases the curve upward.
Data-source operational depth
.
- Claims (FFS): The reference substrate — reliable `fill_date`, `days_supply`, and dispensed quantity. Require continuous medical + pharmacy enrollment from index through follow-up so absent fills are true gaps, not missingness. Real failure modes: 90-day mail-order and bulk fills distort gap timing (a single mail fill covers a stretch that looks like over-supply, and the next gap is mis-dated) — handle with carry-over/stockpiling rules capped at a maximum look-ahead; inpatient/SNF stays can bridge a gap if facility-administered drug is assumed (claims may not show a pharmacy fill during admission) — decide a priori whether hospitalization days suspend the gap clock; free samples and $0 copay cards are invisible. Censor at death, disenrollment, and end of data.
- Claims (Medicare Advantage / capitated): MA-only person-time frequently lacks complete FFS pharmacy claims, so "no fill" is unobserved, not a true discontinuation — exclude MA-only spans or require Part D. In elderly claims, competing risks (death) differ by exposure, so a naive persistence curve compares non-comparable risk sets; CIF is mandatory, not optional.
- EHR (orders only): Order-based "persistence" overstates continuation and cannot detect primary non-adherence; prefer linked dispensing. Visit-driven capture means a patient who leaves the system looks like a discontinuer — distinguish loss-to-follow-up from true stopping.
- Linked claims–EHR / registry: Best of both — EHR/registry severity and mortality plus claims fill completeness — but linkage selects the linkable subset and introduces order/fill/service date discrepancies that must be reconciled before the gap clock starts. Registries strengthen the competing-event (death) ascertainment that the CIF needs.
Worked claims example
Question: 12-month persistence on a newly initiated oral antidiabetic in a commercial + Medicare FFS database, comparing two agents. (1) Cohort: adults with ≥2 type-2-diabetes diagnoses and 365 days of continuous medical + pharmacy enrollment before the first study fill (`index_date`); no fill of the index agent in that washout (new users).
(2) Build the supply timeline: for each `person_id`, order pharmacy fills by `fill_date`; the covered interval of a fill runs from `fill_date` to `fill_date + days_supply`. Apply a carry-over rule — if a refill arrives before the prior supply is exhausted, push the leftover days forward (stockpiling), capped at, say, 30 surplus days to avoid implausible hoarding from 90-day mail fills.
(3) Apply the gap rule: walk the timeline; the first time the next `fill_date` exceeds the running supply-end by ≥ the permissible gap (primary = 60 days; sensitivity = 30 and 90), discontinuation occurs on `supply_end + 1`. (4) Persistence time = `discontinuation_date − index_date`; patients with no qualifying gap are persistent and censored at the earliest of disenrollment, end of data, or day 365; death is a competing event, not plain censoring. (5) Estimate: Kaplan–Meier and cause-specific Cox for the etiologic discontinuation-rate contrast, and the cumulative incidence function (Fine–Gray) for the prognostic "fraction off drug by 12 months," because diabetes cohorts carry non-trivial mortality. (6) Report all three gap lengths; a conclusion that flips between 30- and 90-day gaps is a finding about the algorithm, not the drug.
Decision diagram
flowchart TD
Idx[Index fill: index_date, days_supply] --> Build[Build supply timeline<br/>covered = fill_date .. fill_date+days_supply<br/>carry-over capped]
Build --> Gap{Next fill_date - supply_end >= permissible gap?}
Gap -- No --> Build
Gap -- Yes --> Disc[Discontinuation = supply_end + 1]
Build --> Comp{Death / disenrollment / end of data first?}
Comp -- Death --> CR[Competing event<br/>keep in risk set for CIF]
Comp -- Disenroll/End --> Cen[Censor]
Disc --> Est[Estimand: cause-specific hazard<br/>vs subdistribution / CIF]
CR --> Est
Cen --> Estgantt title Permissible-gap logic for one patient (60-day gap, with carry-over) dateFormat YYYY-MM-DD axisFormat %b %Y section Fills Fill 1 days_supply 30 :f1, 2024-01-01, 30d Fill 2 (refill on time) :f2, 2024-01-29, 30d Fill 3 (late, within gap) :f3, 2024-03-10, 30d section Gap Supply exhausted -> gap clock :crit, g1, 2024-04-09, 60d Discontinuation date (supply_end + 1, backdated) :milestone, d1, 2024-04-10, 0d Gap confirmed (no fill by +60d) :milestone, d2, 2024-06-08, 0d
Worked example
Scenario
Maria starts metformin on January 15, 2024, for her type-2 diabetes. She refills on time twice. Then life gets busy — she never refills again. We are using a 60-day permissible gap: if no fill arrives within 60 days of the day her supply ran out, we call that discontinuation. How many days was Maria persistent on metformin?
Dataset
Raw rows an analyst would see in a claims pharmacy table for patient Maria (person_id 2001). Each row is one filled prescription.
| person_id | fill_date | drug | days_supply |
|---|---|---|---|
| 2001 | 2024-01-15 | metformin | 30 |
| 2001 | 2024-02-14 | metformin | 30 |
| 2001 | 2024-03-15 | metformin | 30 |
Steps
Result
- Label
Persistence = 89 days (Jan 15 to Apr 14, 60-day permissible gap)
- Value
89
Pdc Contrast
PDC would ask a different question over a fixed window — say 180 days: how many of those 180 days did Maria have pills on hand? She had supply Jan 15 through Apr 13 = 89 covered days, so PDC = 89 / 180 = 0.49. Persistence says 'she lasted 89 days before stopping'; PDC says 'she had medication only 49 % of the time we were watching.' Both use the same fills — they just answer different questions. Use persistence when you care how long the patient stayed on therapy; use PDC when you care how fully they covered the treatment window.
Trade-offs
Runnable example
Build treatment episodes and a time-to-discontinuation table from claims-style pharmacy fills, then estimate the competing-risks cumulative incidence of discontinuation. Required inputs (already cleaned, one row per fill): fills : person_id, fill_date (datetime), days_supply (int) # index agent fills only,...
import pandas as pd
import numpy as np
from lifelines import AalenJohansenFitter
PERMISSIBLE_GAP = 60 # days beyond supply exhaustion that defines discontinuation (sensitivity: 30, 90)
MAX_CARRYOVER = 30 # cap on surplus days carried forward (guards against 90-day mail-fill over-supply)
HORIZON = 365 # administrative landmark
def time_to_discontinuation(fills: pd.DataFrame, index: pd.DataFrame, outcome: pd.DataFrame) -> pd.DataFrame:
f = fills.merge(index[["person_id", "index_date"]], on="person_id")
f = f[f["fill_date"] >= f["index_date"]].sort_values(["person_id", "fill_date"])
rows = []
for pid, g in f.groupby("person_id"):
supply_end = None # running covered-through date (with carry-over)
disc_date = None
for fill_date, dsup in zip(g["fill_date"], g["days_supply"]):
if supply_end is None:
supply_end = fill_date + pd.Timedelta(days=int(dsup))
continue
gap = (fill_date - supply_end).days
if gap >= PERMISSIBLE_GAP: # gap rule breached -> discontinued the day after supply ran out
disc_date = supply_end + pd.Timedelta(days=1)
break
carry = max(min((supply_end - fill_date).days, MAX_CARRYOVER), 0) # leftover supply, capped
supply_end = fill_date + pd.Timedelta(days=int(dsup) + carry)
rows.append({"person_id": pid, "last_supply_end": supply_end, "disc_date": disc_date})
epi = pd.DataFrame(rows).merge(index, on="person_id").merge(outcome, on="person_id")
# Competing-risks status: 1 = discontinuation, 2 = death before discontinuation, 0 = censored.
admin = epi["index_date"] + pd.Timedelta(days=HORIZON)
cens = epi[["disenroll_date", "end_date"]].min(axis=1).fillna(admin).clip(upper=admin)
disc = epi["disc_date"]
death = epi["death_date"]
status = np.zeros(len(epi), dtype=int)
event_date = cens.copy()
# earliest of discontinuation / death / censor wins
d_disc = disc.fillna(pd.Timestamp.max)
d_death = death.fillna(pd.Timestamp.max)
disc_first = (d_disc <= cens) & (d_disc <= d_death)
death_first = (d_death <= cens) & (d_death < d_disc)
status = np.where(disc_first, 1, np.where(death_first, 2, 0))
event_date = np.where(disc_first, d_disc, np.where(death_first, d_death, cens))
epi["status"] = status
epi["persistence_days"] = (pd.to_datetime(event_date) - epi["index_date"]).dt.days.clip(lower=0, upper=HORIZON)
return epi[["person_id", "arm", "persistence_days", "status"]]
def cif_discontinuation(epi: pd.DataFrame) -> AalenJohansenFitter:
# Cumulative incidence of discontinuation (event 1) with death (event 2) as competing risk.
ajf = AalenJohansenFitter()
ajf.fit(epi["persistence_days"], epi["status"], event_of_interest=1)
return ajfTreatment-episode construction and competing-risks persistence with data.table + survival/cmprsk. Inputs mirror the Python version: fills : person_id, fill_date (Date), days_supply (integer) index : person_id, index_date (Date), arm outcome: person_id, death_date (Date), disenroll_date (Date), end_date (Date) Death...
library(data.table)
library(survival)
library(cmprsk)
PERMISSIBLE_GAP <- 60L; MAX_CARRYOVER <- 30L; HORIZON <- 365L
persistence_episodes <- function(fills, index, outcome) {
setDT(fills); setDT(index); setDT(outcome)
f <- merge(fills, index[, .(person_id, index_date)], by = "person_id")
f <- f[fill_date >= index_date][order(person_id, fill_date)]
disc <- f[, {
supply_end <- NA; disc_date <- as.Date(NA)
for (i in seq_len(.N)) {
if (is.na(supply_end)) { supply_end <- fill_date[i] + days_supply[i]; next }
gap <- as.integer(fill_date[i] - supply_end)
if (gap >= PERMISSIBLE_GAP) { disc_date <- supply_end + 1L; break }
carry <- max(min(as.integer(supply_end - fill_date[i]), MAX_CARRYOVER), 0L)
supply_end <- fill_date[i] + days_supply[i] + carry
}
.(disc_date = disc_date)
}, by = person_id]
epi <- Reduce(function(a, b) merge(a, b, by = "person_id"), list(disc, index, outcome))
admin <- epi$index_date + HORIZON
cens <- pmin(epi$disenroll_date, epi$end_date, admin, na.rm = TRUE)
d_disc <- fifelse(is.na(epi$disc_date), as.Date("9999-12-31"), epi$disc_date)
d_death <- fifelse(is.na(epi$death_date), as.Date("9999-12-31"), epi$death_date)
disc_first <- d_disc <= cens & d_disc <= d_death
death_first <- d_death <= cens & d_death < d_disc
epi[, status := fifelse(disc_first, 1L, fifelse(death_first, 2L, 0L))]
evt <- fifelse(disc_first, d_disc, fifelse(death_first, d_death, cens))
epi[, persistence_days := pmin(pmax(as.integer(evt - index_date), 0L), HORIZON)]
epi[, .(person_id, arm, persistence_days, status)]
}
# Cause-specific Cox (etiologic) and Fine-Gray subdistribution (prognostic CIF) for the index-drug contrast.
fit_persistence <- function(epi) {
cs <- coxph(Surv(persistence_days, status == 1L) ~ arm, data = epi) # cause-specific hazard of discontinuation
fg <- crr(epi$persistence_days, epi$status, cov1 = model.matrix(~ arm, epi)[, -1, drop = FALSE], failcode = 1, cencode = 0)
list(cause_specific = cs, fine_gray = fg)
}Treatment-episode construction (PROC SQL + data step), Kaplan-Meier persistence curves (PROC LIFETEST), and competing-risks estimation (PROC PHREG cause-specific and eventcode= Fine-Gray). Required inputs (post data-management): work.fills : person_id, fill_date, days_supply (index-agent pharmacy fills only)...
%let gap = 60; /* permissible gap (sensitivity: 30, 90) */
%let carry = 30; /* max carry-over surplus days */
%let horizon = 365;
/* Walk each person's fill timeline; flag the first qualifying gap as discontinuation. */
proc sort data=work.fills; by person_id fill_date; run;
data episodes;
merge work.fills(in=a) work.index(keep=person_id index_date);
by person_id;
retain supply_end disc_date;
format supply_end disc_date date9.;
if first.person_id then do; supply_end = .; disc_date = .; end;
if fill_date >= index_date then do;
if supply_end = . then supply_end = fill_date + days_supply;
else do;
gap = fill_date - supply_end;
if gap >= &gap and disc_date = . then disc_date = supply_end + 1;
else do;
carry = max(min(supply_end - fill_date, &carry), 0);
supply_end = fill_date + days_supply + carry;
end;
end;
end;
if last.person_id then output;
keep person_id index_date disc_date;
run;
/* Assign competing-risks status (1=discontinuation, 2=death, 0=censored) and persistence_days. */
proc sql;
create table surv as
select e.person_id, i.arm, e.index_date,
min(o.disenroll_date, o.end_date, e.index_date + &horizon) as cens format=date9.,
e.disc_date, o.death_date,
case when e.disc_date ne . and e.disc_date <= calculated cens
and (o.death_date = . or e.disc_date <= o.death_date) then 1
when o.death_date ne . and o.death_date <= calculated cens
and (e.disc_date = . or o.death_date < e.disc_date) then 2
else 0 end as status,
min(max(coalesce(case calculated status when 1 then e.disc_date
when 2 then o.death_date
else calculated cens end) - e.index_date, 0), &horizon)
as persistence_days
from episodes e
inner join work.index i on e.person_id = i.person_id
inner join work.outcome o on e.person_id = o.person_id;
quit;
/* Kaplan-Meier persistence curves by arm (treats only event=1 as the failure). */
proc lifetest data=surv plots=survival(atrisk);
time persistence_days*status(0 2); /* censor codes 0 and 2 -> naive KM; see CIF below for competing risks */
strata arm;
run;
/* Competing-risks CIF of discontinuation with death as competitor (Fine-Gray subdistribution). */
proc phreg data=surv plots(overlay)=cif;
class arm;
model persistence_days*status(0) = arm / eventcode=1; /* status=2 (death) handled as competing event */
hazardratio arm;
run;
/* Cause-specific hazard of discontinuation (etiologic contrast). */
proc phreg data=surv;
class arm;
model persistence_days*status(0 2) = arm; /* death censored -> cause-specific */
hazardratio arm;
run;Citations
- [1]Cramer JA, Roy A, Burrell A, et al. Medication compliance and persistence: terminology and definitions. Value Health. 2008;11(1):44-47.
- [2]Andrade SE, Kahler KH, Frech F, Chan KA. Methods for evaluation of medication adherence and persistence using automated databases. Pharmacoepidemiol Drug Saf. 2006;15(8):565-574.
- [3]Vrijens B, De Geest S, Hughes DA, et al.; ABC Project Team. A new taxonomy for describing and defining adherence to medications. Br J Clin Pharmacol. 2012;73(5):691-705.
- [4]Raebel MA, Schmittdiel J, Karter AJ, Konieczny JL, Steiner JF. Standardizing terminology and definitions of medication adherence and persistence in research employing electronic databases. Med Care. 2013;51(8 Suppl 3):S11-S21.