Three-state occupancy from PFS and OS curves in a partitioned survival model

Gives the proportions progression-free, progressed and dead at time t from the PFS and OS survival functions of one treatment arm. The PFS curve gives the progression-free share directly, the dead share is 1 minus OS, and the progressed share is the gap between the two curves. Each arm has its own pair of curves.

Signature

P_PF = S_PFS; P_PD = S_OS - S_PFS; P_D = 1 - S_OS
Inputs
InputsDefinitionUnit
S_PFSProgression-free survival at time t since model entry: the probability of being alive with no progression, where progression or death ends PFSprobability from 0 to 1
S_OSOverall survival at time t since model entry, the probability of being aliveprobability from 0 to 1
Output
P_PFProportion of the starting cohort alive and progression-free at time tproportion from 0 to 1
P_PDProportion of the starting cohort alive after progression at time tproportion from 0 to 1
P_DProportion of the starting cohort dead at time tproportion from 0 to 1

Function

Partitioned survival state occupancy and area-under-the-curve QALY function

Maps a set of survival curves that are not mutually exclusive, usually progression-free survival (PFS) and overall survival (OS) for each treatment arm, to the share of the cohort in each health state over time, and then to mean time in each state and QALYs as utility-weighted areas under the curves. State membership is read from the curves rather than built from transition probabilities, which is what separates a partitioned survival model from a state transition model. Linked records cover the parts this package does not repeat: the restricted mean from a Kaplan-Meier curve (HE-FM-ADMC-003), QALYs summed over periods (HE-FM-QALY-001), discounted totals (HE-FM-DR-002), a treatment curve from a baseline curve and a hazard ratio (HE-FM-HR-003) and the continuous discount rate (HE-FM-CONT-003).

Computational function

  • Computational function: partitioned survival occupancy, crossing check, life years and QALYs from PFS and OS curves on a time grid

    Takes PFS and OS as a spreadsheet or a fitted model usually holds them, as survival values on a grid of times from model entry, and returns the state shares at each time, a count of grid times at which PFS lies above OS, and discounted life years and QALYs per person. It applies HE-FM-PSM-001 at each grid time, takes the areas between grid times by the trapezoid rule, discounts each interval at its midpoint and values the areas as in HE-FM-PSM-003. The inputs therefore differ from the formula's variables: the formula works at one time t, while the function works on whole curves, a grid and a discount rate, and it declines to value crossed curves rather than patching them.

    Inputs and outputs: t: Grid times from model entry, increasing and starting at 0; the last time is the time horizon. Unit: years.; S_PFS: PFS at each grid time, 1 at time 0. Unit: probability from 0 to 1.; S_OS: OS at each grid time, 1 at time 0. Unit: probability from 0 to 1.; u_PF: Utility for a year progression-free. Unit: utility on the scale where 1 is full health.; u_PD: Utility for a year progressed. Unit: utility on the same scale.; d: Annual discount rate, 0 for undiscounted results. Unit: proportion per year.; P_PD: Progressed share at each grid time, OS minus PFS. Unit: proportion.; P_D: Dead share at each grid time, 1 minus OS. Unit: proportion.; n_cross: Number of grid times at which PFS exceeds OS. Unit: count.; LY: Discounted life years per person to the horizon. Unit: years.; QALY: Discounted QALYs per person to the horizon, returned as missing when n_cross is above 0. Unit: QALYs.

    Assumption: Survival changes close to linearly between grid times, so the trapezoid areas approach the exact areas as the grid gets finer. Utilities are constant within each state and death carries zero. Discounting uses the factor 1/(1+d) raised to the midpoint time of each interval.

    Worked example (Three-year annual grid, checked by hand): The article's standard-care curves read at whole years over a three-year horizon give about 1.1058 QALYs. The exact areas to three years give about 1.0748, so the annual grid overstates by about 2.9%. t = (0, 1, 2, 3); S_PFS = (1, 0.367879, 0.135335, 0.049787); S_OS = (1, 0.606531, 0.367879, 0.22313); u_PF = 0.75; u_PD = 0.60; d = 0; P_PD = (0, 0.238652, 0.232544, 0.173343); P_D = (0, 0.393469, 0.632121, 0.77687); n_cross = 0; LY = 1.585975; QALY = 1.105801

    Worked example (Monthly grid to 50 years, undiscounted): The standard-care curves on a monthly grid return about 1.3503 QALYs and 2.0003 life years against the exact lifetime values of 1.35 and 2.0 in HE-EX-PSM-004. t = (0, 0.083333, ..., 50); S_PFS = exp(-1.0 * t); S_OS = exp(-0.5 * t); u_PF = 0.75; u_PD = 0.60; d = 0; n_cross = 0; LY = 2.000289; QALY = 1.350260

    Worked example (Monthly grid discounted at 3.5% a year): The same curves discounted at the NICE reference-case rate return about 1.2680 QALYs, against 1.2678 from the closed form in HE-EX-PSM-008. t = (0, 0.083333, ..., 50); S_PFS = exp(-1.0 * t); S_OS = exp(-0.5 * t); u_PF = 0.75; u_PD = 0.60; d = 0.035; n_cross = 0; LY = 1.871504; QALY = 1.267995

    Worked example (Crossed curves are not valued): A PFS hazard of 0.4 with an OS hazard of 0.5 a year puts PFS above OS at every grid time after 0, so the function reports 600 crossings and returns no QALYs. S_PFS = exp(-0.4 * t); S_OS = exp(-0.5 * t); n_cross = 600; QALY = NA

    Excel: =C2-B2 and =1-C2 filled down give the progressed and dead shares, with time in column A, PFS in column B and OS in column C over rows 2 to 602; =SUMPRODUCT(--(B2:B602>C2:C602)) gives n_cross into a cell named ncross; =SUMPRODUCT((C2:C601+C3:C602)/2*(A3:A602-A2:A601)/(1+DiscRate)^((A2:A601+A3:A602)/2)) gives LY; and =IF(ncross>0,NA(),SUMPRODUCT(((B2:B601+B3:B602)/2*UtilPF+((C2:C601-B2:B601)+(C3:C602-B3:B602))/2*UtilPD)*(A3:A602-A2:A601)/(1+DiscRate)^((A2:A601+A3:A602)/2))) gives QALYs, with the utilities in UtilPF and UtilPD and the annual rate in DiscRate.

    R: psm_qaly <- function(t, s_pfs, s_os, u_pf, u_pd, d = 0) { n_cross <- sum(s_pfs > s_os); k <- seq_along(t)[-1]; w <- diff(t)/(1+d)^((t[k-1]+t[k])/2); pd <- s_os-s_pfs; a_pf <- sum(w*(s_pfs[k-1]+s_pfs[k])/2); a_pd <- sum(w*(pd[k-1]+pd[k])/2); list(P_PD = pd, P_D = 1-s_os, n_cross = n_cross, LY = a_pf+a_pd, QALY = if (n_cross > 0) NA else u_pf*a_pf+u_pd*a_pd) } Vectorised over the grid; psm_qaly(0:3, exp(-(0:3)), exp(-0.5*(0:3)), 0.75, 0.60)$QALY gives about 1.105801 from unrounded curve values.

    Python: def psm_qaly(t, s_pfs, s_os, u_pf, u_pd, d=0): t, sp, so = (np.asarray(x, float) for x in (t, s_pfs, s_os)); n_cross = int((sp > so).sum()); w = np.diff(t)/(1+d)**((t[:-1]+t[1:])/2); pd = so-sp; a_pf = (w*(sp[:-1]+sp[1:])/2).sum(); a_pd = (w*(pd[:-1]+pd[1:])/2).sum(); return pd, 1-so, n_cross, a_pf+a_pd, (None if n_cross else u_pf*a_pf+u_pd*a_pd) Uses numpy imported as np and returns the progressed and dead shares, n_cross, LY and QALYs.

    Test (Monthly grid reproduces the exponential closed form): On a monthly grid to 50 years built from constant hazards, the QALYs in a cell named QALYgrid are within 0.001 of HE-FM-PSM-004. Expected result: TRUE. Excel check: =ABS(QALYgrid-(UtilPF/(HazPFS+LN(1+DiscRate))+UtilPD*(1/(HazOS+LN(1+DiscRate))-1/(HazPFS+LN(1+DiscRate)))))<0.001

    Test (Equal utilities turn QALYs into utility times life years): Setting UtilPD equal to UtilPF, the QALYs in a cell named QALYequal equal that utility times LY. Expected result: TRUE. FALSE shows that the progressed area is not OS minus PFS. Excel check: =ABS(QALYequal-UtilPF*LY)<1E-9

    Common error (Setting OS equal to PFS instead of fixing the curves): Setting one curve equal to the other wherever they cross removes the negative shares but hides the inconsistency. In TA285 the ERG considered setting OS equal to PFS in crossed probabilistic draws inappropriate. The function returns no QALYs so that the curves are refitted or the choice is reported.

    Common error (Reading the curves only at whole years): An annual grid on steep curves overstates the areas: about 1.1058 QALYs over three years in the standard-care example against about 1.0748 exactly, an overstatement of about 2.9%. A monthly grid brings the lifetime result to within about 0.0003 of the exact 1.35.

    Source: Woods B, Sideris E, Palmer S, Latimer N, Soares M. NICE DSU Technical Support Document 19: partitioned survival analysis for decision modelling in health care: a critical review. Sheffield: Decision Support Unit, ScHARR, University of Sheffield; 2017. Section 2.2 on state membership from PFS and OS, section 4.1.2 on PFS curves that lie above OS because endpoints are modelled independently, and section 3.2.9 on TA285. National Institute for Health and Care Excellence. NICE technology appraisal and highly specialised technologies guidance: the manual (PMG36). London: NICE; 2022, last updated 31 March 2026. Section 4.5.1: costs and health effects discounted at 3.5% a year in the reference case.

    P_PD_k = S_OS_k - S_PFS_k; P_D_k = 1 - S_OS_k; n_cross = sum_k [S_PFS_k > S_OS_k]; w_k = (t_k - t_(k-1)) / (1 + d)^((t_(k-1) + t_k) / 2); LY = sum_k [w_k * (S_OS_(k-1) + S_OS_k) / 2]; QALY = sum_k [w_k * (u_PF * (S_PFS_(k-1) + S_PFS_k) / 2 + u_PD * (P_PD_(k-1) + P_PD_k) / 2)]

Try this function

Implementations

  • Excel

    Partitioned survival state shares from two curve columns

    With time in column A, PFS in column B and OS in column C from row 2, the three formulas entered in columns D, E and F and filled down return the progression-free, progressed and dead shares.

    =B2; =C2-B2; =1-C2

Assumptions

  • PFS and OS from the same arm and time origin

    Both curves describe the same treatment arm and population, are measured from model entry and are read at the same time t. PFS counts death without progression as an event, so the true PFS curve cannot lie above OS.

  • Forward-only movement through the partitioned states

    People move only forward, from progression-free to progressed to dead, which TSD 19 regards as generally reasonable in advanced cancer. Each curve is modelled independently, so the formula imposes no link between progression and later mortality: a treatment effect on progression changes the dead share only if the OS curve itself changes.

Worked examples

  • Standard care occupancy at one year in the partitioned survival example

    With exponential PFS and OS hazards of 1.0 and 0.5 a year, the article's standard-care arm has 36.8% progression-free, 23.9% progressed and 39.3% dead at one year.

    S_PFS = 0.36788; S_OS = 0.60653; P_PF = 0.36788; P_PD = 0.23865; P_D = 0.39347
  • New treatment occupancy at one year in the partitioned survival example

    With hazards of 2/3 and 0.4 a year, hazard ratios of about 0.67 and 0.8 applied to standard care as in HE-FM-HR-003, the new treatment has 51.3% progression-free, 15.7% progressed and 33.0% dead at one year, as in the article.

    S_PFS = 0.51342; S_OS = 0.67032; P_PF = 0.51342; P_PD = 0.15690; P_D = 0.32968

Common errors

  • Separately extrapolated PFS curve crossing above OS

    Because PFS and OS are fitted and extrapolated independently, the PFS curve can rise above OS, implying more people progression-free than alive. With an extrapolated PFS hazard of 0.4 and an OS hazard of 0.5 a year, PFS at one year is about 0.670 against OS of about 0.607, and the progressed share is about minus 0.064. TSD 19 reports that the adjustment used to avoid this in TA295 was criticised by the ERG and not accepted by the committee.

  • Setting OS equal to PFS in probabilistic draws where the curves cross

    Most appraisals reviewed in TSD 19 sampled PFS and OS independently in probabilistic sensitivity analysis, so some draws can cross even when the base case does not. In TA285 the company set OS equal to PFS in those simulations, an approach the ERG considered inappropriate. Doing so hides the inconsistency and does not model the correlation between endpoints.

Sources

  • Partitioned survival state membership in DSU TSD 19

    Woods B, Sideris E, Palmer S, Latimer N, Soares M. NICE DSU Technical Support Document 19: partitioned survival analysis for decision modelling in health care: a critical review. Sheffield: Decision Support Unit, ScHARR, University of Sheffield; 2017. Section 2.2 and Figure 1: the PFS curve gives the progression-free share, the dead state is 1 minus the OS curve and the progressed state is the difference between the OS and PFS curves; section 3.2.8 (TA295) and section 3.2.9 (TA285 and independent sampling of endpoints).

    View source →

Canonical Identity