Real-World Progression and rwPFS
An abstracted or algorithmically derived oncology endpoint in which real-world disease progression (from radiology reports, clinician notes, or treatment-pattern proxies) is combined with death into a real-world progression-free survival (rwPFS) time, requiring an explicit progression definition, an assessment cadence, and a pre-specified estimand for how death is handled.
In plain language
Real-world progression-free survival (rwPFS) measures how long a cancer patient went without their disease getting worse or dying, using information collected during routine clinical care rather than a controlled research trial. Instead of a formal scan protocol, researchers read the patient's radiology reports and doctors' notes and assign a date when disease growth was first recorded — this is called real-world progression, or rwP. The clock starts when the patient begins their treatment line and stops at whichever comes first: the rwP date or the date of death. Because this information is pulled from notes and imaging that were done for clinical care and not research, the quality of the endpoint depends entirely on how carefully those records are read and how clearly the abstractor can tell worsening disease from other changes.
Real-world progression (rwP)
is the analogue of RECIST-defined progression for routinely collected oncology data, where prospective imaging at protocol intervals does not exist. Because there is no central read and no fixed scan schedule, rwP must be constructed: most often by structured human abstraction of radiology reports and clinician notes into a binary "progression event" with a date, sometimes by an NLP/algorithmic pass over the same documents, and sometimes by treatment-pattern proxies (a new line of therapy, or discontinuation framed as progression). Real-world progression-free survival (rwPFS) is then the time from an index date (typically treatment line start) to the earlier of the first rwP event or death, with censoring for those who reach end of follow-up event-free. rwPFS is a composite endpoint: progression OR death, exactly as trial PFS is — but the ascertainment of the progression component is the entire methodological problem.
Core estimand distinction
. Three estimand choices are doing the work and must be pre-specified, not discovered in code. (1) What counts as an event. rwPFS proper counts progression-or-death; if you instead model real-world time to progression (rwTTP) you treat progression as the event and death without prior progression as a competing risk, which changes both the model and the interpretation. A Kaplan-Meier curve that censors competing deaths estimates the (counterfactual) progression rate in a world without death and overstates the real-world cumulative incidence of progression; a cumulative incidence function from a Fine-Gray or Aalen-Johansen estimator answers the actually-observed question. For rwPFS the composite makes death an event, so a cause-specific Cox/KM is appropriate; the competing-risks subtlety is unavoidable only when you decompose the composite. (2) Assessment cadence and interval censoring. Progression is detected only when a scan or note exists, so the event date is the assessment date, not the true biologic date — events are interval-censored and the observed cadence differs by arm, site, and insurer. (3) Index/time-zero alignment, which determines whether immortal time contaminates the line-of-therapy start. Get these wrong and rwPFS is internally inconsistent across arms even before any confounding adjustment.
Pros, cons, and trade-offs
. - vs real-world overall survival (rwOS): rwOS needs only a reliable death date (a single, high-validity field when linked to NDI/SSA/obituary sources) and has no ascertainment cadence problem, so it is the most defensible real-world endpoint. rwPFS trades that robustness for earlier signal and larger effect sizes, but inherits abstraction error, cadence-driven informative assessment, and weaker correlation with rwOS than trial PFS has with OS. Prefer rwOS as the anchor; use rwPFS as a supportive/intermediate endpoint with a demonstrated surrogacy/association analysis (e.g., correlation of rwPFS with rwOS within the same source). - vs trial RECIST PFS: RECIST PFS has fixed scan schedules, central/blinded read, and unidimensional lesion rules; rwP has none of these. The advantage is generalizability and cost; the cost is that rwP is not RECIST and should never be relabeled as such. Prefer trial PFS for efficacy claims; use rwP for external comparators, label-expansion context, and HTA effectiveness arguments where the trial population is not the question. - vs treatment-pattern proxies (next-line / discontinuation as progression): proxies are cheap and computable from claims alone but conflate progression with toxicity, patient preference, financial barriers, and formulary-driven switching, and they systematically lag the true event. Prefer abstracted/NLP rwP when chart or report text is available; reserve next-line proxies for claims-only feasibility or sensitivity analyses, never as the primary definition for a comparative claim.
When to use
. Single-arm trial external control arms and comparative effectiveness in oncology where OS alone is too slow or too confounded by post-progression therapy; HTA submissions needing real-world effectiveness alongside trial efficacy; settings with abstractable radiology/clinician text (Flatiron-style enriched EHR, integrated delivery networks, registries with adjudicated progression). Always pair rwPFS with rwOS and report the abstraction method, assessment cadence, and the rwPFS-rwOS association.
When NOT to use — and when it is actively misleading or dangerous
. - Claims-only data with no chart/report text. You cannot ascertain progression from claims; a next-line-of- therapy proxy is not rwPFS and presenting it as such is misleading. It mechanically favors the arm with fewer downstream options and lags the true event by the time-to-next-treatment decision. - Differential assessment cadence by arm. If one drug is monitored more intensively (more frequent scans), it will detect progression earlier and look worse on rwPFS purely from surveillance — surveillance/ascertainment bias. Diagnose by comparing scan/assessment frequency across arms before trusting any contrast. - Decomposing the composite without competing-risks handling. Reporting a KM "time to progression" that censors competing deaths overstates progression incidence and is dangerous when one arm has higher early mortality (its censored deaths inflate its apparent progression-free time). Use a cumulative incidence function. - Immortal time at line start. Defining the index as a confirmed line start that requires surviving long enough to receive a second cycle builds immortal time into rwPFS; align time zero to the first administration. - Cross-source pooling without a harmonized progression definition. A radiology-anchored rwP from one vendor and a clinician-anchored rwP from another are different endpoints; pooling them silently is uninterpretable.
Data-source operational depth
. - Claims (FFS vs MA): Claims contain no progression signal; the best obtainable surrogate is a coded new line of systemic therapy or a new metastatic-site diagnosis, both lagged and noisy. Require continuous medical + pharmacy enrollment so a "no new therapy" period is observed, not missing — Medicare Advantage encounter data are incomplete relative to fee-for-service, so MA-only person-time can fabricate apparent progression-free intervals from unobserved care; exclude MA-only spans or flag them. Infusion vs oral (Part B vs Part D) routing changes where the next-line signal even appears. - EHR / enriched EHR (Flatiron-style): rwP comes from trained abstractors reading radiology and oncologist notes, or from NLP. Failure modes: inter-abstractor variability, notes that say "mixed response" or "clinical progression" without imaging, external-care leakage (scans done outside the network never enter the chart), and assessment cadence driven by clinic visit frequency rather than biology, which interval-censors events on an irregular, arm-dependent grid. Workarounds: dual abstraction with adjudication, capture the assessment date and the progression date separately, and report visit/scan frequency by arm. - Registry: Often has adjudicated progression and stage but sparse longitudinal imaging; strong for the progression definition (clinically reviewed) but weak for cadence completeness and for full therapy history — link to claims for treatment lines and to a death index for the OS/composite component. - Linked EHR-claims-mortality: The ideal substrate: EHR text for rwP, claims for complete therapy lines and the next-line proxy as a cross-check, and NDI/obituary mortality for the death component of the composite. Cost: linkage selection (only the linkable subset), and order/scan/service-date discrepancies that must be reconciled before assigning the progression and index dates.
Worked example (enriched-EHR rwPFS with a claims cross-check)
Question: rwPFS for first-line therapy A vs B in advanced NSCLC. (1) Index/time zero = date of first administration of the line-defining agent (from EHR administrations, confirmed against the J-code infusion claim or Part D fill on or near that date); do not require a second cycle, which would inject immortal time. (2) Progression component: abstractor-confirmed rwP date from radiology/clinician notes; record both the assessment date and the abstracted progression date, and store the ascertainment method. (3) Death component: death date from the linked mortality index, not the last-EHR-activity date (which biases survival). (4) rwPFS event = earlier of rwP date and death date; rwPFS time = that date minus index. Censor at last confirmed disease assessment (not last-any-contact) for patients event-free, so that someone lost from the system is censored at their last informative scan, not carried as progression-free indefinitely. (5) Claims cross-check: derive a next-line-of-therapy date from the pharmacy/medical claims (first fill or infusion of a non-A/non-B regimen after a 30-day gap, requiring continuous non-MA-only enrollment), and report the rwPFS-vs-rwTTNT agreement as a sensitivity analysis. (6) Report rwPFS and rwOS side by side with their within-cohort correlation, and show assessment-frequency by arm to rule out surveillance bias. (7) Estimand check: the primary contrast is a cause-specific hazard ratio for the composite (Cox); the supportive rwTTP decomposition uses a cumulative incidence function with death as a competing event.
Interpreting the output
. For Patient 2201 with NSCLC, the index date is January 10, 2023. A radiology report dated May 2, 2023 — day 112 from index — documents progression per investigator interpretation; a death record is dated August 14, 2023 (day 216). The algorithm assigns rwPFS = 112 days, event type = progression (not death, because progression occurred first).
Formal interpretation: rwPFS = 112 days means the patient was progression-free for 112 days from treatment initiation, with progression identified from a clinical note between the last no-progression assessment and the date of the note documenting progression. The assigned date — May 2 — is an ascertainment date, not a true biological progression date. If the patient's last scan was 8 weeks earlier and the next was May 2, the true progression could have occurred any time in that 8-week interval. rwPFS is therefore interval-censored at the assessment frequency, which is partly a function of the visit schedule at the treating institution, not the treatment under study. This makes rwPFS non-comparable to trial RECIST PFS, where assessments occur at protocol-fixed intervals in both arms.
Practical interpretation: always report the median assessment interval alongside rwPFS estimates, and examine whether assessment frequency differs between treatment arms — a difference in visit frequency is a surveillance bias risk, not a treatment signal. For cross-study comparisons, rwPFS is best interpreted as a relative measure within the same real-world cohort rather than a literal equivalent of trial PFS.
Worked example
Scenario
A single patient with advanced non-small-cell lung cancer starts first-line immunotherapy on 2023-01-10. An analyst in an enriched EHR database wants to calculate this patient's rwPFS. The patient has a CT scan on 2023-05-02 whose radiology report is abstracted as showing new nodal involvement — the abstractor records a real-world progression event on that date. The patient dies on 2023-08-14. Because progression occurred before death, rwPFS runs from treatment start to the progression date. The analyst records 112 days as the rwPFS time and flags it as an event (not censored).
Dataset
One-row-per-patient analytic table showing the three key dates pulled from the EHR for this patient.
| person_id | index_date | progression_date | death_date | abstraction_source |
|---|---|---|---|---|
| 2201 | 2023-01-10 | 2023-05-02 | 2023-08-14 | radiology report 2023-05-02: new mediastinal nodes |
Steps
Identify the index date: the date of first administration of the line-defining agent, 2023-01-10. This is day zero for the follow-up clock.
Identify the progression date: the abstractor read the 2023-05-02 CT report and recorded it as real-world progression based on language indicating new nodal involvement. The assessment date (2023-05-02) becomes the progression date — the actual biologic onset is unknown.
Identify the death date from the linked mortality record: 2023-08-14.
Apply the composite rule: rwPFS event = the earlier of progression date and death date. Here 2023-05-02 (progression) comes before 2023-08-14 (death), so progression is the event.
Calculate rwPFS time: from 2023-01-10 to 2023-05-02 = 112 days.
Set the event flag to 1 (an event occurred — progression was reached before the end of follow-up). The patient is NOT censored.
Result
rwPFS = 112 days (event = 1, progression-first). Death occurred 104 days later at day 216 from index but does not change the rwPFS calculation because progression was already the earlier event.
Timeline Spec
- Title
rwPFS for one advanced NSCLC patient: treatment start to real-world progression or death
- Window
- Start
2023-01-10
- End
2023-08-14
- Label
Observation window: treatment start to death (day 0 to day 216)
- Events
- Label
Treatment start (index date)
- Start
2023-01-10
- Quantity
Day 0 — first immunotherapy administration
- Label
CT scan abstracted as real-world progression
- Start
2023-05-02
- Marker Day
112
- Quantity
Day 112 — radiology report: new mediastinal nodes (rwP event)
- Label
Death
- Start
2023-08-14
- Marker Day
216
- Quantity
Day 216 — patient death (after rwP already recorded)
- Spans
- Kind
followup
- Start
2023-01-10
- End
2023-05-02
- Label
Progression-free time: 112 days (rwPFS)
- Kind
exposed
- Start
2023-05-02
- End
2023-08-14
- Label
Post-progression survival: 104 days (not part of rwPFS)
- Result
- Label
rwPFS = 112 days (event = progression on day 112); death at day 216 does not shorten rwPFS because progression came first
- Rwpfs Days
112
- Event Type
progression
- Death Day
216
- Caption
The horizontal bar spans from treatment start (day 0, 2023-01-10) to death (day 216, 2023-08-14). The orange marker at day 112 (2023-05-02) is the abstracted real-world progression event from the CT report. The green span from day 0 to day 112 is the progression-free interval and equals the patient's rwPFS. The gray span from day 112 to day 216 is post-progression survival — it is observed in the data but is not counted in rwPFS.
- Alt Text
A single horizontal patient timeline from day 0 (2023-01-10) to day 216 (2023-08-14). An orange event marker at day 112 labels the abstracted real-world progression event from the CT scan report. A green bar spanning day 0 to day 112 is labeled 112-day rwPFS. A gray bar spanning day 112 to day 216 is labeled post-progression survival. A red marker at day 216 labels the death event.
Runnable example
python implementation
Construct rwPFS time and event from abstracted progression, linked mortality, and disease-assessment dates, then fit a cause-specific Cox model for the composite. Required inputs (cleaned, one row per person unless noted): cohort : person_id, index_date...
import pandas as pd
import numpy as np
from lifelines import CoxPHFitter
def build_rwpfs(cohort: pd.DataFrame, rwp: pd.DataFrame,
death: pd.DataFrame, lastvis: pd.DataFrame) -> pd.DataFrame:
df = (cohort
.merge(rwp[["person_id", "progression_date"]], on="person_id", how="left")
.merge(death[["person_id", "death_date"]], on="person_id", how="left")
.merge(lastvis[["person_id", "last_assessment_date"]], on="person_id", how="left"))
# Composite event date = earliest of progression or death (NaT if neither occurred).
ev = df[["progression_date", "death_date"]].min(axis=1)
# Event flag and the date used for the time calculation:
# - if an event occurred, use the event date;
# - otherwise censor at the LAST CONFIRMED ASSESSMENT (not last-any-contact),
# so someone who left the system is not carried as progression-free forever.
df["rwpfs_event"] = ev.notna().astype(int)
end_date = ev.where(ev.notna(), df["last_assessment_date"])
df["rwpfs_days"] = (end_date - df["index_date"]).dt.days
df = df[df["rwpfs_days"] >= 0] # drop pre-index anomalies (date-reconciliation errors)
# For a competing-risks decomposition (rwTTP), classify by which date is EARLIEST:
# progression first (or death missing) -> 1; death first (or progression missing) -> 2.
prog_first = df["progression_date"].notna() & (
df["death_date"].isna() | (df["progression_date"] <= df["death_date"]))
death_first = df["death_date"].notna() & (
df["progression_date"].isna() | (df["death_date"] < df["progression_date"]))
df["event_type"] = np.select(
[prog_first, death_first],
[1, 2], default=0) # 1=progression, 2=competing death, 0=censored
return df
# Cause-specific Cox for the COMPOSITE rwPFS (progression OR death as the event).
rwpfs = build_rwpfs(cohort, rwp, death, lastvis)
model = rwpfs.merge(covariates, on="person_id") # covariates measured in the pre-index window only
cph = CoxPHFitter()
cph.fit(model[["rwpfs_days", "rwpfs_event", "arm", *covariate_cols]],
duration_col="rwpfs_days", event_col="rwpfs_event")
cph.print_summary()r implementation
Same rwPFS construction in R: composite event = earlier of rwP or death, censoring event-free patients at the last confirmed assessment. Fits the composite with survival::coxph and the progression-vs-competing-death cumulative incidence with the Fine-Gray...
library(data.table); library(survival); library(cmprsk)
build_rwpfs <- function(cohort, rwp, death, lastvis) {
setDT(cohort); setDT(rwp); setDT(death); setDT(lastvis)
df <- Reduce(function(a, b) merge(a, b, by = "person_id", all.x = TRUE),
list(cohort,
rwp[, .(person_id, progression_date)],
death[, .(person_id, death_date)],
lastvis[, .(person_id, last_assessment_date)]))
df[, ev_date := pmin(progression_date, death_date, na.rm = TRUE)] # earliest event, NA if none
df[, rwpfs_event := as.integer(!is.na(ev_date))]
df[, end_date := fifelse(is.na(ev_date), last_assessment_date, ev_date)] # censor at last assessment
df[, rwpfs_days := as.integer(end_date - index_date)]
df <- df[rwpfs_days >= 0]
# Competing-risks status for the rwTTP decomposition, classified by the EARLIEST date:
# progression first (or death missing) = 1; death first (or progression missing) = 2; else censored.
df[, event_type := fifelse(
!is.na(progression_date) & (is.na(death_date) | progression_date <= death_date), 1L,
fifelse(!is.na(death_date) & (is.na(progression_date) | death_date < progression_date), 2L, 0L))]
df[]
}
rwpfs <- build_rwpfs(cohort, rwp, death, lastvis)
model <- merge(rwpfs, covariates, by = "person_id") # pre-index covariates only
# Composite rwPFS: cause-specific Cox (progression OR death is the event).
coxph(Surv(rwpfs_days, rwpfs_event) ~ arm + ., data = model[, !c("person_id","event_type")])
# Supportive rwTTP: Fine-Gray subdistribution hazard, death (code 2) as the competing event.
crr(ftime = model$rwpfs_days, fstatus = model$event_type,
cov1 = model.matrix(~ arm, model)[, -1, drop = FALSE], failcode = 1, cencode = 0)