Treatment Patterns and Lines of Therapy (LOT)
An algorithmic exposure-construction method that converts a longitudinal sequence of drug fills or administrations in claims/EHR data into discrete, ordered lines of therapy (LOT1, LOT2, ...) and characterizes initiation, persistence, switching, augmentation/add-on, and advancement to the next line over time.
On this page
Explore this method family
Lines of therapy (LOT) describe the ordered sequence of drug regimens a patient moves through over time, like chapters in their treatment story. An algorithm reads a patient's prescription fills in claims data, decides when one regimen ends and the next begins — based on a patient stopping one drug and starting another (a switch) or going without any fills for too long (a gap) — and labels each chapter LOT1, LOT2, and so on. The result tells researchers how many patients ever reach a second or third treatment, what drugs they moved to, and how long each treatment chapter lasted. It cannot see drugs given in a doctor's office that are billed separately from the pharmacy, so some infused cancer drugs may be invisible to a pharmacy-only analysis.
Treatment patterns and lines of therapy (LOT)
are an exposure-definition construct, not an estimator: the deliverable is a derived, analysis-ready exposure variable (a per-patient ordered set of regimens with start/stop dates and a `lot_number`) built deterministically from temporal fill/administration sequences. Downstream comparative analyses (survival, HCRU, cost) then treat that variable as the exposure. Because the variable is constructed by an algorithm, its validity is a property of the rules — gap length, minimum claims per line, add-on vs substitution logic, and progression triggers — and those rules must be pre-specified and, for regulatory or HTA use, validated against medical chart review with reported agreement statistics (e.g., kappa, line-count and regimen concordance). LOT algorithms should be developed or reviewed with practicing clinicians familiar with the disease and its guidelines; in oncology the FLAURA vs CheckMate-style line conventions, maintenance-therapy handling, and combination-regimen windows are not derivable from fills alone.
Core conceptual distinction
three things must be separated and pre-specified.
- A line vs an episode of a single drug: a line is a regimen (one or more drugs started together within a short combination window, e.g., 28 days) carried forward until it ends.
- What ends a line and starts the next one: the canonical events are a switch (a new agent not in the current regimen, with the prior agent stopped — substitution), an augmentation/add-on (a new agent added while the prior agent continues — this does NOT advance the line under most oncology conventions but DOES under some chronic-disease conventions, so it must be declared), and a gap-then-restart (the regimen lapses beyond a permissible gap and a later fill begins a new line).
- The estimand the LOT feeds: time-to-next-treatment-or-death (TTNTD) and time-to-discontinuation are duration estimands defined within a line; line-of-therapy distribution and attrition (the share reaching LOT2, LOT3) are sequencing estimands defined across lines. The same fill data yield different numbers under a cause-specific hazard for "advance to next line" (treating death as a censoring event) versus a Fine–Gray subdistribution for the cumulative incidence of advancement (treating death as a competing event) — in older oncology cohorts where death is common, this choice materially changes the reported share advancing and must be stated in the estimand, not chosen post hoc.
Pros, cons, and trade-offs
(specific and comparative).
- vs persistence / time-to-discontinuation (single-drug): LOT captures sequencing and advancement — what the patient moves to after the index regimen fails, progresses, or causes toxicity — which persistence alone cannot describe. Persistence is one ingredient (it defines when a line lapses), but a persistence analysis answers "how long on drug A," whereas LOT answers "A then B then C." Prefer LOT in oncology, rheumatology, MS, and any progressive/multi-regimen disease. Cost: LOT rules are disease- and algorithm-specific, far less standardized than a simple permissible-gap persistence rule, and chart validation is resource-intensive.
- vs a cascade-of-care / funnel analysis: the cascade is a population funnel from diagnosis through linkage, treatment, and control; LOT is the post-initiation sequencing engine inside the treated arm of that funnel. They are complementary, not substitutes. Cost: LOT says nothing about the undiagnosed/untreated upstream losses.
- vs treating each NDC fill as the exposure (no line construction): raw fills overcount "treatments" — sample fills, bridging, mail-order stockpiling, and dose splits all look like distinct events — and cannot express regimens or advancement. LOT collapses these correctly but at the price of analyst-defined windows that, if mis-set, manufacture or erase lines. Prefer raw fills only for pure utilization counting where sequencing is irrelevant.
When NOT to use — and when it is actively misleading or dangerous
- No clinical anchor for "line" exists for the disease. In conditions treated with continuous single-agent therapy and no orderly sequencing (most uncomplicated hypertension), forcing LOT manufactures structure that does not exist; use persistence/switching instead.
- The data cannot see the regimen. Provider-administered oncolytics billed under the medical benefit (J-codes, HCPCS), inpatient chemotherapy bundled into a DRG, and 340B/buy-and-bill arrangements are frequently invisible or incompletely coded in pharmacy-only datasets — an algorithm run on pharmacy fills alone will silently drop entire lines and over-report watch-and-wait. Require medical-claim drug capture before claiming LOT in oncology.
- Immortal time and procedure-anchored lines. If a line's start is keyed to a procedure (e.g., surgery, transplant) and follow-up is measured from an earlier landmark (diagnosis), the interval in which the patient must survive to receive that line is immortal — advancement rates and within-line survival are inflated. Anchor each line's time zero to its own first fill/administration.
- Differential competing risks by exposure. In elderly claims cohorts, sicker first-line regimens are followed by higher early mortality; a cause-specific "advance to next line" analysis that censors those deaths overstates the share advancing in the sicker arm relative to a Fine–Gray subdistribution view. Pre-specify the competing-risk handling.
- Maintenance miscounted as a new line. In ovarian cancer and lymphoma, PARP inhibitors or rituximab maintenance started after active therapy are part of the same line under modern conventions; coding them as LOT2 inflates line counts (the Simmons et al. validation found first-line maintenance regimen-match required explicit maintenance rules to reach agreement).
Data-source operational depth
- Claims (FFS or commercial): Pharmacy fills give NDC + `fill_date` + `days_supply`; provider-administered drugs are in medical claims as HCPCS/J-codes with a service date but no days_supply, so durations must be imputed from cycle schedules. Require continuous medical AND pharmacy enrollment across the baseline and follow-up so that "no further fill" is true discontinuation, not unobserved care. Failure mode: Medicare Advantage encounter data lack the complete fee-for-service claim stream — MA-only person-time produces phantom gaps and missing lines; restrict to enrollees with Parts A/B/D (or a complete commercial medical+pharmacy benefit) and exclude MA-only spans. Failure mode: sample fills, 90-day mail order, and stockpiling distort `days_supply`, shifting gap-defined line boundaries. Failure mode: differential competing risks by exposure in elderly claims bias advancement estimates (see above).
- EHR: Orders and medication-administration records (MAR) capture provider-administered oncolytics that pharmacy claims miss, and problem lists/labs/staging sharpen progression triggers; but visit-driven capture means a patient who receives a line outside the system is differentially lost, and an unobserved out-of-network line looks like discontinuation. Prefer EHR linked to claims to reassemble the full regimen history.
- Registry: Often records protocol-defined lines and adjudicated progression prospectively — the gold standard for validating a claims LOT algorithm — but typically incomplete for the full longitudinal pharmacy stream and for out-of-registry care.
- Linked claims–EHR–registry: The ideal substrate (medical-benefit drug capture + staging/progression + complete enrollment), but linkage selects the linkable subset and introduces order/fill/service date discrepancies that must be reconciled before assigning line start dates.
Worked claims example
Question: real-world LOT distribution and time-to-LOT2 in metastatic non–small-cell lung cancer in a commercial + Medicare FFS database with medical-benefit drug capture.
- Cohort: adults with ≥2 mNSCLC diagnoses, 365 days of continuous A/B/D (or commercial medical+pharmacy) enrollment before the first antineoplastic, and exclude MA-only person-time.
- Antineoplastic events: union of pharmacy NDC fills and medical-claim HCPCS/J-code administrations for the curated mNSCLC drug list, each with a `service_date` and (for fills) `days_supply`.
- LOT1 start: the first antineoplastic `service_date` after the metastatic-diagnosis washout.
- Regimen window: all distinct agents within 28 days of LOT1 start form the LOT1 regimen (combination capture).
- Line advancement: LOT2 begins at the first event of an agent not in the LOT1 regimen accompanied by stopping ≥1 LOT1 agent (substitution), OR the first antineoplastic after a permissible gap of >90 days following the LOT1 regimen's last `days_supply` end (restart); an added agent that continues alongside the full LOT1 regimen is logged as augmentation and does NOT advance the line.
- Maintenance rule: a single-agent continuation (e.g., pemetrexed/immunotherapy maintenance) after a defined induction is held within LOT1, not counted as LOT2.
- Estimand: cumulative incidence of reaching LOT2 with death as a competing event (Fine–Gray), reported alongside the cause-specific advancement hazard; time-to-LOT2 measured from LOT1 start.
- Sensitivity: vary the combination window (14/28/42 days), permissible gap (60/90/120 days), and the medical-benefit drug list; report chart-validation agreement (line count, first-line regimen match) before the algorithm is used for decisions.
Decision diagram
flowchart TD
Ev[Next antineoplastic event<br/>drug, service_date, days_supply] --> Win{Within COMBO_WINDOW<br/>of current line start?}
Win -- Yes --> Reg[Add drug to current regimen<br/>extend supply coverage]
Win -- No --> Sub{New agent NOT in<br/>current regimen<br/>AND prior agent stopped?}
Sub -- Yes --> Switch[Advance line:<br/>SWITCH / substitution]
Sub -- No --> Gap{Gap since regimen<br/>supply end > GAP_DAYS?}
Gap -- Yes --> Restart[Advance line:<br/>GAP-then-RESTART]
Gap -- No --> Aug[Same line:<br/>AUGMENTATION / add-on<br/>does NOT advance]
Switch --> Next[lot_number + 1<br/>start new regimen]
Restart --> Nextgantt title One mNSCLC patient's fills mapped to lines of therapy dateFormat YYYY-MM-DD axisFormat %b %Y section LOT1 Carboplatin + pemetrexed (induction) :done, l1a, 2023-01-10, 84d Pemetrexed maintenance (held within LOT1) :active, l1b, 2023-04-04, 120d section Gap Lapse > 90 days (no antineoplastic) :crit, gap, 2023-08-02, 95d section LOT2 Docetaxel restart (new regimen) :l2, 2023-11-05, 63d
Worked example
Scenario
Patient 7042 has metastatic non-small-cell lung cancer (mNSCLC). Their oncologist starts them on erlotinib, an oral targeted therapy. The patient fills erlotinib three times between February and late March 2023, then goes completely off treatment for 95 days. In late July they start docetaxel, a chemotherapy drug. We want to know: how many lines of therapy did this patient have, what was in each line, and how many days passed from the start of line 1 to the start of line 2?
Dataset
Pharmacy claims table — one row per fill, exactly as an analyst sees it.
| person_id | fill_date | drug | days_supply |
|---|---|---|---|
| 7042 | 2023-02-01 | erlotinib | 30 |
| 7042 | 2023-03-01 | erlotinib | 30 |
| 7042 | 2023-03-28 | erlotinib | 30 |
| 7042 | 2023-07-31 | docetaxel | 21 |
Steps
Result
Patient 7042 had 2 lines of therapy. LOT1 regimen = erlotinib, started 2023-02-01, ended 2023-04-26. LOT2 regimen = docetaxel, started 2023-07-31. Advancement reason = gap-then-restart with a new agent (95-day gap exceeded the 90-day permissible threshold). Time-to-LOT2 = 180 days.
Trade-offs
Runnable example
Pharmacy + medical-benefit LOT construction from claims-style inputs. Required input (already cleaned, de-duplicated, restricted to the curated antineoplastic code list, and filtered to continuously enrolled non-MA-only person-time): tx : one row per antineoplastic event -> person_id, drug (generic/class string),...
import pandas as pd
COMBO_WINDOW = pd.Timedelta(days=28) # agents starting within this of a line start = same regimen
GAP_DAYS = 90 # permissible gap; a fill after a longer lapse starts a new line
DEFAULT_DOS = 30 # fallback days_supply for administered drugs with no duration
def build_lot(tx: pd.DataFrame) -> pd.DataFrame:
tx = tx.sort_values(["person_id", "service_date"]).copy()
tx["days_supply"] = tx["days_supply"].fillna(DEFAULT_DOS).astype(int)
tx["supply_end"] = tx["service_date"] + pd.to_timedelta(tx["days_supply"], unit="D")
lines = []
for pid, g in tx.groupby("person_id", sort=False):
g = g.reset_index(drop=True)
lot = 1
line_start = g.loc[0, "service_date"]
regimen = set() # agents in the current line's regimen
line_supply_end = line_start # latest supply coverage of regimen agents
reason = "initiation"
def flush(end):
lines.append({"person_id": pid, "lot_number": lot,
"regimen": "+".join(sorted(regimen)),
"line_start": line_start, "line_end": end,
"advance_reason": reason})
for _, row in g.iterrows():
d, drug, send = row["service_date"], row["drug"], row["supply_end"]
if (d - line_start) <= COMBO_WINDOW: # still assembling the regimen
regimen.add(drug); line_supply_end = max(line_supply_end, send); continue
gap = (d - line_supply_end).days
substitution = drug not in regimen # new agent not in current regimen
if substitution or gap > GAP_DAYS: # advance to the next line
flush(line_supply_end)
lot += 1
line_start, regimen = d, {drug}
line_supply_end = send
reason = "switch/substitution" if (substitution and gap <= GAP_DAYS) else "gap_restart"
else: # continuation or augmentation (same line)
regimen.add(drug); line_supply_end = max(line_supply_end, send)
flush(line_supply_end)
return pd.DataFrame(lines).sort_values(["person_id", "lot_number"])Pharmacy + medical-benefit LOT construction with data.table, mirroring the Python logic and parameters. Required input: tx : data.table -> person_id, drug (character), service_date (Date), days_supply (integer;
library(data.table)
COMBO_WINDOW <- 28L # days: agents starting within this of a line start = same regimen
GAP_DAYS <- 90L # permissible gap before a later fill starts a new line
DEFAULT_DOS <- 30L # fallback days_supply for administered drugs
build_lot <- function(tx) {
setDT(tx)
tx[is.na(days_supply), days_supply := DEFAULT_DOS]
tx[, supply_end := service_date + days_supply]
setorder(tx, person_id, service_date)
one_person <- function(g) {
lot <- 1L; line_start <- g$service_date[1L]
regimen <- character(0); line_supply_end <- line_start; reason <- "initiation"
out <- list()
flush <- function(end) list(lot_number = lot,
regimen = paste(sort(unique(regimen)), collapse = "+"),
line_start = line_start, line_end = end, advance_reason = reason)
for (i in seq_len(nrow(g))) {
d <- g$service_date[i]; drug <- g$drug[i]; send <- g$supply_end[i]
if (as.integer(d - line_start) <= COMBO_WINDOW) { # assembling the regimen
regimen <- union(regimen, drug); line_supply_end <- max(line_supply_end, send); next
}
gap <- as.integer(d - line_supply_end)
substitution <- !(drug %in% regimen)
if (substitution || gap > GAP_DAYS) { # advance to next line
out[[length(out) + 1L]] <- flush(line_supply_end)
lot <- lot + 1L; line_start <- d; regimen <- drug
line_supply_end <- send
reason <- if (substitution && gap <= GAP_DAYS) "switch/substitution" else "gap_restart"
} else { # continuation / augmentation (same line)
regimen <- union(regimen, drug); line_supply_end <- max(line_supply_end, send)
}
}
out[[length(out) + 1L]] <- flush(line_supply_end)
rbindlist(out)
}
tx[, one_person(.SD), by = person_id]
}Pharmacy + medical-benefit LOT construction in SAS using PROC SQL prep plus a sorted DATA step with RETAIN/LAG to walk each patient's events chronologically and emit lot_number. Required input (post data-management, restricted to the antineoplastic code list and continuously enrolled non-MA-only person-time):...
%let combo_window = 28; /* days: agents within this of a line start share the regimen */
%let gap_days = 90; /* permissible gap before a later fill starts a new line */
%let default_dos = 30; /* fallback days_supply for administered (J-code) drugs */
/* Impute administered-drug durations and compute supply coverage end. */
proc sql;
create table tx2 as
select person_id, drug, service_date,
coalesce(days_supply, &default_dos) as days_supply,
service_date + coalesce(days_supply, &default_dos) as supply_end format=date9.
from work.tx;
quit;
proc sort data=tx2; by person_id service_date; run;
/* Walk each patient's events; RETAIN line state and advance on substitution or gap. */
data work.lot;
set tx2;
by person_id;
retain lot_number line_start line_supply_end;
length advance_reason $20 ;
if first.person_id then do;
lot_number = 1; line_start = service_date; line_supply_end = supply_end;
advance_reason = 'initiation'; return;
end;
/* Still inside the combination window -> same regimen, extend coverage. */
if (service_date - line_start) <= &combo_window then do;
line_supply_end = max(line_supply_end, supply_end);
advance_reason = 'combination';
end;
else do;
gap = service_date - line_supply_end;
/* substitution would be confirmed against the regimen member set built in a companion */
/* hash/lookup; here a gap beyond the threshold advances the line. */
if gap > &gap_days then do;
lot_number = lot_number + 1; line_start = service_date;
line_supply_end = supply_end; advance_reason = 'gap_restart';
end;
else do; /* within gap: continuation or augmentation of the current line */
line_supply_end = max(line_supply_end, supply_end);
advance_reason = 'augmentation_or_continuation';
end;
end;
run;
/* Regimen-level table: one row per (person_id, lot_number) with the agent set. */
proc sql;
create table work.lot_regimen as
select person_id, lot_number,
min(service_date) as line_start format=date9.,
max(line_supply_end) as line_end format=date9.,
count(distinct drug) as n_agents
from work.lot
group by person_id, lot_number;
quit;Citations
- [1]Andrade SE, Kahler KH, Frech F, Chan KA. Methods for evaluation of medication adherence and persistence using automated databases. Pharmacoepidemiology and Drug Safety. 2006;15(8):565-574.
- [2]Meng W, Ou W, Chandwani S, Chen X, Black W, Cai Z. Temporal phenotyping by mining healthcare data to derive lines of therapy for cancer. Journal of Biomedical Informatics. 2019;100:103335.