Partitioned Survival Model
A cohort cost-effectiveness model for (typically oncology) decision analysis that derives state membership at each cycle directly from independently extrapolated overall-survival and progression-free-survival curves, rather than from explicit transition probabilities between health states.
In plain language
A partitioned survival model is a tool health economists use to estimate how long patients with cancer spend in three states — living without disease progression, living after their disease has progressed, and dead — over a lifetime horizon. It works by taking two survival curves from a clinical trial (one tracking when patients progressed, one tracking when they died) and, at every point in time, reads the share of patients in each state directly from those curves. Because trial follow-up is usually short, both curves are mathematically extended far into the future, and the credibility of the whole model depends heavily on how trustworthy that extension is.
A partitioned survival model (PSM), also called an area-under-the-curve model, is a decision-analytic cost-effectiveness structure most commonly used for oncology technology appraisals. It represents three mutually exclusive health states — progression-free, progressed disease, and dead — but, unlike a Markov / state-transition model (STM), it never specifies transition probabilities between those states. Instead it takes two independently estimated survival curves — overall survival (OS) and progression-free survival (PFS) — and partitions the population at each model cycle: the proportion alive and progression-free is read directly as S_PFS(t); the proportion dead is 1 − S_OS(t); and the proportion alive with progressed disease is the residual, S_OS(t) − S_PFS(t). State occupancy is the area under each curve, costs and QALYs are accrued by multiplying time spent in each state by state costs and utilities, and the incremental cost-effectiveness ratio (ICER) or net monetary benefit (NMB) follows. Because trial follow-up is short relative to the lifetime horizon decision-makers require, both curves are typically parametrically extrapolated beyond the observed data, which makes PSMs only as credible as their extrapolation assumptions.
Core conceptual distinction
The defining feature is that transitions are not modelled — survival functions are. In a Markov/STM you estimate the hazard of moving progression-free → progressed, progression-free → dead, and progressed → dead, then propagate a cohort through a transition matrix; OS and PFS emerge as outputs. In a PSM you reverse this: OS and PFS are inputs fitted independently, and progressed-disease occupancy is a subtraction. The consequence is subtle but decision-relevant: because OS and PFS are fitted separately, nothing guarantees the implied post-progression survival (PPS) is clinically plausible — it can even imply a negative number at risk if the extrapolated PFS curve crosses above the OS curve, a structural impossibility the PSM does not enforce. The estimand a PSM targets is mean (discounted, quality-adjusted) survival in each state over a lifetime horizon — a modelled population-mean, not a within-patient transition rate. If the decision question is fundamentally about the timing and dependence of disease progression on subsequent death (e.g., a treatment that delays progression but whose PPS depends on subsequent therapy), the PSM's independence assumption is the wrong representation.
Pros, cons, and trade-offs
- vs Markov / state-transition models (STM): PSMs are simpler to build, require only OS and PFS (no PPS or transition-specific data, which RWE often cannot identify cleanly), and are transparent to reviewers because the inputs are the familiar trial Kaplan–Meier curves. Cost: they impose structural independence between OS and PFS, cannot use external/registry evidence on post-progression survival, and can produce internally inconsistent state occupancy (negative PPS, implausible long-term mortality). Prefer a PSM for short-horizon, single-line decisions where OS and PFS are mature and a PPS model is not identifiable; prefer an STM when post-progression survival is long, depends on subsequent treatment, or when you have external data to inform the progressed→dead hazard. - vs a simple three-state Markov with constant transition probabilities: the PSM uses the full shape of the survival curves rather than collapsing to exponential/constant hazards, so it captures non-proportional and time-varying hazards naturally. Cost: that flexibility lives entirely in the extrapolation choice, which is judgment-heavy and often the single largest driver of the ICER. - vs reporting trial-period restricted mean survival time (RMST) only: RMST is assumption-light but answers a different question (in-trial mean survival to a fixed horizon); a PSM is required when the HTA decision needs a lifetime cost-effectiveness estimate, accepting the extrapolation burden that RMST avoids.
When to use
Lifetime cost-utility analysis of oncology (or analogous progressive-disease) therapies for HTA submission (NICE, CDA-AMC (formerly CADTH), PBAC, ICER-US), where the pivotal evidence reports OS and PFS, follow-up is immature, a three-state structure is clinically reasonable, and post-progression survival cannot be modelled directly from identifiable transition data. PSMs are the de facto base-case structure in NICE oncology appraisals.
When NOT to use — and when it is actively misleading or dangerous
- When OS and PFS extrapolations cross or imply implausible PPS. If the fitted PFS curve crosses above OS at any point in the horizon, progressed-disease occupancy goes negative — a structural impossibility. Decisions built on such a model are not just imprecise, they are invalid; switch to an STM that constrains PPS ≥ 0. - When post-progression survival drives the decision and depends on later therapy. A PSM cannot represent the progressed→dead hazard as a function of subsequent treatment, crossover, or external evidence; using one here can materially over- or under-state lifetime benefit. - When the curves are extrapolated far beyond mature data with no external anchor. With <50% of OS events observed, the lifetime mean is dominated by the unobserved tail, and the choice among Weibull/log-logistic/log-normal/generalized gamma/spline fits can swing the ICER across the willingness-to-pay threshold. Presenting one curve as the answer is misleading; full structural and extrapolation sensitivity analysis is mandatory. - When the disease is not adequately captured by three states (e.g., multiple progression lines, treatment-free intervals, or a meaningful cured fraction better handled by a mixture-cure model).
Data-source operational depth
PSMs are usually built from trial OS/PFS, but RWE increasingly supplies the curves, the extrapolation anchor (long-term registry/claims survival), or an external comparator arm — each with distinct failure modes. - Claims: Death is the cleanest endpoint only when mortality is linked (Medicare linked to NDI/vital records, or a closed system); raw claims under-capture death because disenrollment and end-of-data look identical to survival — naive Kaplan–Meier on claims-only mortality is biased upward and inflates the OS tail used to extrapolate. Worse, Medicare Advantage (MA) person-time lacks fee-for-service (FFS) claims, so encounter-based progression proxies (new metastatic codes, second-line regimen initiation, imaging escalation) are differentially missing for MA enrollees; if MA penetration differs by the population feeding OS vs PFS, the two curves are estimated on non-comparable person-time and the partition is corrupted. "Progression" in claims is itself a proxy (second-line therapy start, new radiotherapy, hospice election), not RECIST progression, so claims-derived PFS systematically differs from trial PFS and the two cannot be naively combined in one model. - EHR: Progression can be richer (imaging reports, oncologist notes, ECOG), but is encounter-driven and left/right-censored by network leakage — a patient who progresses and dies outside the system looks censored, biasing both PFS and OS. Death must be reconciled against an external index; structured "last contact" is not death. - Registry: Often the best anchor for the long-term OS tail (population cancer registries with active follow-up), but progression is frequently not collected, so a registry can inform OS extrapolation while PFS still comes from the trial — a hybrid that must be checked for population transportability (stage mix, calendar period, treatment era). - Linked claims–EHR–registry: The ideal substrate (EHR progression + claims utilization/cost + registry/vital-records mortality), but linkage selection and date discrepancies between imaging date, regimen-change date, and service date must be reconciled before either curve is estimated, or the partition inherits the misalignment.
Worked example (RWE-anchored oncology PSM)
Question: lifetime cost-utility of a new first-line therapy vs standard of care in metastatic NSCLC, pivotal trial with 24-month median follow-up, lifetime horizon 20 years, 3-month cycles, 3.5% annual discounting. (1) From the trial, fit OS and PFS independently; with only ~40% of OS events observed, fit a panel of parametric models (exponential, Weibull, Gompertz, log-logistic, log-normal, generalized gamma) and compare by AIC/BIC and visual/clinical plausibility of the extrapolated tail. (2) Anchor the OS tail to a real-world source: a SEER-Medicare cohort of stage IV NSCLC initiators, where mortality is reliable because Medicare claims are linked to vital records — but restrict to continuously enrolled FFS Parts A/B beneficiaries and exclude MA-only person-time, because MA enrollees' death and progression are under-captured. Use the registry tail to reject implausibly optimistic trial extrapolations (e.g., a log-normal fit implying 18% 10-year survival when SEER shows <5%). (3) At each cycle t, set progression-free occupancy = S_PFS(t), dead = 1 − S_OS(t), progressed = S_OS(t) − S_PFS(t); add an explicit check that progressed ≥ 0 at every cycle and, if violated, constrain or switch to an STM. (4) Attach state costs (drug, administration, monitoring, progressed-disease management, terminal care from the last 90 days of claims) and utilities (progression-free vs progressed) to the area under each curve. (5) Compute discounted total costs and QALYs per arm, the incremental cost, incremental QALYs, ICER and NMB at the threshold. (6) Run the mandatory sensitivity suite: alternative parametric extrapolations (structural uncertainty), the registry-anchored vs trial-only tail, alternative utility sources, and a probabilistic sensitivity analysis; report the ICER's sensitivity to the OS extrapolation as the headline driver, because in immature oncology data it almost always is.
Interpreting the output
A 10-year partitioned survival model returns: PF life-years = 2.34 yr, Progressed life-years = 1.74 yr, Dead fraction = 5.92 yr (sum = 10.00 yr), with S_PFS < S_OS confirmed at all time points.
Formal interpretation. At each time point t, state membership is derived from two independently fitted survival curves: progression-free survival S_PFS(t) and overall survival S_OS(t). The partition is: P(PF, t) = S_PFS(t), P(Dead, t) = 1 − S_OS(t), and P(Progressed, t) = S_OS(t) − S_PFS(t). Because S_PFS and S_OS are modeled separately — not from a joint distribution — the model implicitly assumes they are statistically independent after conditioning on treatment arm, which is rarely satisfied in practice. The structural validity constraint S_PFS(t) ≤ S_OS(t) for all t must be verified computationally; crossing curves invalidate the model and must be corrected by constraining or re-parameterizing the extrapolations before the model is used in a submission.
Practical interpretation. The 1.74 progressed-disease life-years per patient carry distinct cost and utility weights that drive the HEOR model output. Sensitivity analyses should test alternative parametric families for each curve independently and as a pair, because the denominator of QALY calculations depends on the relative area under S_OS(t) − S_PFS(t). Any scenario where the OS extrapolation is more optimistic than PFS produces negative time in the progressed state — a biological impossibility that must be caught and corrected before submission to HTA bodies.
Worked example
Scenario
An oncology cost-effectiveness model compares a new first-line therapy to standard of care in metastatic lung cancer. The model uses a 10-year horizon divided into 1-year cycles. From the clinical trial, two survival curves have been estimated and extended to 10 years. At year 0 all 1,000 patients are alive and progression-free. The table below shows what the two curves say about each 1-year interval, and the arithmetic shows how total time in each state is calculated.
Dataset
Survival curve values read at the start of each year (S_PFS and S_OS as proportions of the original cohort still in that state). Each value is taken directly from the extrapolated curves.
| year | S_PFS (proportion progression-free) | S_OS (proportion alive) | progression-free state | progressed state | dead state |
|---|---|---|---|---|---|
| 1.0 | 1.0 | 1.0 | |||
| 1 | 0.6 | 0.8 | 0.6 | 0.2 | 0.2 |
| 2 | 0.36 | 0.64 | 0.36 | 0.28 | 0.36 |
| 3 | 0.2 | 0.5 | 0.2 | 0.3 | 0.5 |
| 4 | 0.1 | 0.38 | 0.1 | 0.28 | 0.62 |
| 5 | 0.05 | 0.28 | 0.05 | 0.23 | 0.72 |
| 6 | 0.02 | 0.2 | 0.02 | 0.18 | 0.8 |
| 7 | 0.01 | 0.14 | 0.01 | 0.13 | 0.86 |
| 8 | 0.09 | 0.09 | 0.91 | ||
| 9 | 0.05 | 0.05 | 0.95 |
Steps
At each year t, the proportion of patients who are progression-free equals S_PFS(t) directly — for example, at year 1 that is 0.60 (60 out of every 100 patients).
The proportion who are dead equals 1 minus S_OS(t) — at year 1 that is 1 - 0.80 = 0.20 (20 out of 100).
The proportion who are alive but progressed is the leftover: S_OS(t) minus S_PFS(t) — at year 1 that is 0.80 - 0.60 = 0.20 (20 out of 100). These three shares must always add up to 1.00.
Time spent in each state over the 10-year horizon is the area under each curve: sum the state proportion across all 10 one-year intervals and multiply by the 1-year cycle length.
Progression-free time = 1.00 + 0.60 + 0.36 + 0.20 + 0.10 + 0.05 + 0.02 + 0.01 + 0.00 + 0.00 = 2.34 years.
Progressed time = 0.00 + 0.20 + 0.28 + 0.30 + 0.28 + 0.23 + 0.18 + 0.13 + 0.09 + 0.05 = 1.74 years.
Dead time accounts for the rest: 10.00 - 2.34 - 1.74 = 5.92 years (equivalently, total area above the OS curve).
A quick sanity check: 2.34 + 1.74 + 5.92 = 10.00. The three states account for all 10 years, confirming the partition is internally consistent.
To check validity, confirm that S_PFS never exceeds S_OS in any row — if it did, the progressed share would go negative, which is impossible and would mean the model needs to be fixed or replaced with a different model structure.
Result
Over a 10-year horizon, an average patient spends 2.34 years progression-free, 1.74 years alive with progressed disease, and 5.92 years in the dead state (model time accounting). The partition table: Progression-free = 2.34 yr | Progressed = 1.74 yr | Dead = 5.92 yr | Total = 10.00 yr. These times are then multiplied by their respective costs and quality weights (QALYs) to produce the final cost-effectiveness result.
Partition Table
Summary: time spent in each health state per patient over the 10-year model horizon
| Health state | How it is calculated | Years per patient |
|---|---|---|
| Progression-free | Area under PFS curve | 2.34 |
| Progressed (alive) | Area between PFS and OS curves | 1.74 |
| Dead | Area above OS curve (remainder) | 5.92 |
| TOTAL | 10.00 |
Runnable example
python implementation
Partitioned survival model engine. Required inputs (already estimated upstream): surv : one row per (cycle index t, arm) with columns cycle (int, 0..n), arm (str), s_pfs (float in [0,1]), s_os (float in [0,1]) # s_pfs, s_os are the EXTRAPOLATED survivor...
import pandas as pd
import numpy as np
def partition_states(surv: pd.DataFrame) -> pd.DataFrame:
# Partition: PF = S_PFS ; Dead = 1 - S_OS ; Progressed = S_OS - S_PFS (the residual).
out = surv.copy()
out["pf"] = out["s_pfs"]
out["dead"] = 1.0 - out["s_os"]
out["progressed"] = out["s_os"] - out["s_pfs"]
# STRUCTURAL VALIDITY: extrapolated PFS must never exceed OS, else occupancy is negative.
bad = out.loc[out["progressed"] < -1e-9]
if len(bad):
raise ValueError(
f"PFS crosses above OS in {len(bad)} cycle-arms (negative progressed occupancy); "
"constrain the curves or switch to a state-transition model."
)
return out
def run_psm(surv: pd.DataFrame, params: dict) -> pd.DataFrame:
cyc_yr = params["cycle_years"] # e.g. 0.25 for 3-month cycles
r = params["disc_rate"] # annual discount rate, e.g. 0.035
st = partition_states(surv)
# Discount factor at the cycle midpoint (half-cycle correction via midpoint timing).
st["t_yr"] = (st["cycle"] + 0.5) * cyc_yr
st["disc"] = 1.0 / (1.0 + r) ** st["t_yr"]
# Per-cycle cost = sum over states of (occupancy * annual state cost * cycle length) + arm drug cost while PF.
c = params["state_cost"]; u = params["state_util"]; drug = params["drug_cost"]
st["cost"] = (
(st["pf"] * c["pf"]
+ st["progressed"] * c["progressed"]) * cyc_yr
+ st["pf"] * st["arm"].map(drug) * cyc_yr # active-treatment cost accrues while progression-free
)
# QALYs = sum over alive states of (occupancy * utility * cycle length).
st["qaly"] = (st["pf"] * u["pf"] + st["progressed"] * u["progressed"]) * cyc_yr
st["d_cost"] = st["cost"] * st["disc"]
st["d_qaly"] = st["qaly"] * st["disc"]
agg = st.groupby("arm")[["d_cost", "d_qaly"]].sum()
treated, ref = params["treated_arm"], params["reference_arm"]
inc_cost = agg.loc[treated, "d_cost"] - agg.loc[ref, "d_cost"]
inc_qaly = agg.loc[treated, "d_qaly"] - agg.loc[ref, "d_qaly"]
agg["icer"] = np.nan
agg.loc[treated, "icer"] = inc_cost / inc_qaly # incremental cost per QALY
wtp = params["wtp"]
agg.loc[treated, "nmb"] = inc_qaly * wtp - inc_cost # incremental net monetary benefit
return aggr implementation
Partitioned survival model in R. `surv` is a data.frame with one row per (cycle, arm): cycle (integer 0..n), arm (character), s_pfs, s_os -- extrapolated survivor functions. `params` is a list of cycle length (years), discount rate, state costs/utilities,...
library(dplyr)
run_psm <- function(surv, params) {
st <- surv %>%
mutate(
pf = s_pfs,
dead = 1 - s_os,
progressed = s_os - s_pfs
)
# Structural validity: extrapolated PFS must not exceed OS (no negative progressed occupancy).
if (any(st$progressed < -1e-9)) {
stop("PFS crosses above OS (negative progressed occupancy); constrain curves or use a state-transition model.")
}
r <- params$disc_rate # annual discount rate, e.g. 0.035
cyc_yr <- params$cycle_years # e.g. 0.25 for 3-month cycles
st <- st %>%
mutate(
t_yr = (cycle + 0.5) * cyc_yr, # cycle-midpoint timing (half-cycle correction)
disc = 1 / (1 + r) ^ t_yr,
drug = params$drug_cost[arm],
cost = (pf * params$state_cost[["pf"]] +
progressed * params$state_cost[["progressed"]]) * cyc_yr +
pf * drug * cyc_yr, # active-treatment cost while progression-free
qaly = (pf * params$state_util[["pf"]] +
progressed * params$state_util[["progressed"]]) * cyc_yr,
d_cost = cost * disc,
d_qaly = qaly * disc
)
agg <- st %>% group_by(arm) %>%
summarise(d_cost = sum(d_cost), d_qaly = sum(d_qaly), .groups = "drop")
tr <- params$treated_arm; rf <- params$reference_arm
inc_cost <- agg$d_cost[agg$arm == tr] - agg$d_cost[agg$arm == rf]
inc_qaly <- agg$d_qaly[agg$arm == tr] - agg$d_qaly[agg$arm == rf]
list(per_arm = agg,
icer = inc_cost / inc_qaly, # incremental cost per QALY
nmb = inc_qaly * params$wtp - inc_cost) # incremental net monetary benefit
}