Skip to contents

Model and source

  • Citation: Zecchin C, Gueorguieva I, Enas NH, Friberg LE. Models for change in tumour size, appearance of new lesions and survival probability in patients with advanced epithelial ovarian cancer. Br J Clin Pharmacol. 2016;82(3):717-727. doi:10.1111/bcp.12994. DDMORE Foundation Model Repository: DDMODEL00000218. Subject-specific tumour-dynamics covariates (KG, KD0, KD1, IBASE) are empirical-Bayes outputs from the upstream SLD model (DDMODEL00000217; see modellib(‘Zecchin_2016_tumorovarian’)).
  • Description: Time-to-event model for overall survival (OS) in advanced epithelial ovarian cancer (Zecchin 2016 / DDMODEL00000218): Weibull baseline hazard with covariate effects of normalised baseline SLD (TUM_SLD / 70 mm), tumour-size-ratio TSR(t) capped at week 12, time-varying new-lesion indicator (NWLS), and binary ECOG performance status, with the underlying SLD trajectory (subject-specific tumour-growth and drug-cytotoxicity rate constants from the upstream Zecchin 2016 SLD model) integrated inline.
  • Article: https://doi.org/10.1111/bcp.12994
  • DDMORE Foundation Model Repository entry: DDMODEL00000218
  • Source bundle (local mirror): dpastoor/ddmore_scraping/218/

This vignette validates the Zecchin 2016 overall-survival (OS) model packaged under inst/modeldb/ddmore/Zecchin_2016_survival.R. The model is a time-to-event Weibull-baseline-hazard model linked to an inline tumour-size (SLD) ODE; subject-specific tumour-growth and drug-cytotoxicity rate constants (KG, KD0, KD1, IBASE) are empirical-Bayes posteriors carried in via the dataset from the upstream Zecchin 2016 SLD model (Zecchin_2016_tumorovarian / DDMODEL00000217).

The DDMORE bundle ships only an OS-fit listing; the linked publication (Zecchin 2016 BJCP 82(3):717-727; PMID 27136318; PMC5338128) reports the final estimates in Table 2 of the paper. The publication PDF was not on disk for this extraction; Methods / Table 2 cross-checks were performed via PMC HTML.

Population

Zecchin 2016 (Br J Clin Pharmacol 82(3):717-727) modelled overall survival in N = 336 women with advanced (FIGO stage III/IV) epithelial ovarian cancer enrolled in a randomised Phase III chemotherapy trial. Treatment arms were carboplatin monotherapy (target AUC 5.0 mg·min/mL Q3W) or carboplatin (target AUC 4.0 mg·min/mL Q3W) plus gemcitabine. The cohort had a median age of about 59 years; baseline ECOG performance-status distribution was concentrated at 0 and 1 (the paper dichotomises ECOG to 0 vs ≥1 because of the small number of patients with ECOG > 1 at enrolment); median baseline sum of longest diameters (SLD) was about 70 mm (the paper’s reference value TVSLD0).

The same information is available programmatically:

m  <- readModelDb("Zecchin_2016_survival")()
str(m$meta$population, max.level = 1)
#> List of 8
#>  $ n_subjects    : int 336
#>  $ n_studies     : int 1
#>  $ age_range     : chr "median ~59 years (advanced epithelial ovarian cancer cohort; Zecchin 2016 Table S2 / paper text)"
#>  $ weight_range  : chr "not transcribed in this extraction (Zecchin 2016 Table S2 captures the demographic distributions; the WebFetch "| __truncated__
#>  $ sex_female_pct: num 100
#>  $ disease_state : chr "advanced (FIGO stage III/IV) epithelial ovarian cancer (recurrent / platinum-sensitive cohort, randomised Phase"| __truncated__
#>  $ dose_range    : chr "Phase III chemotherapy: carboplatin monotherapy (target AUC 5.0 mg*min/mL Q3W) or carboplatin (target AUC 4.0 m"| __truncated__
#>  $ notes         : chr "336 patients pooled from a randomised Phase III trial in advanced epithelial ovarian cancer (Zecchin 2016, BJCP"| __truncated__

Source trace

Per-parameter origin is captured as in-file comments next to each ini() entry in inst/modeldb/ddmore/Zecchin_2016_survival.R. The table below collects them in one place.

Equation / parameter Value Source location
Weibull baseline hazard h(t) n/a Executable_OS.mod $DES (DADT(2) = LAM*SHP*(LAM*(T+DEL))**(SHP-1)*EXP(...)) ↔︎ Zecchin 2016 Equation 7
SLD ODE d/dt(tumorSize) n/a Executable_OS.mod $DES DADT(1) ↔︎ same equation in Zecchin 2016 SLD model (Eq. 1-3 of the paper / DDMODEL00000217)
TSR(t) clamp at week 12 n/a Executable_OS.mod $DES IF(T.LE.84) WTS = TSR ELSE WTS = WTS
lam_haz (Weibull scale, 1/day) exp-1 = 0.001183 (1/d) Output_real_OS.lst FINAL TH1 = 1.18E-03; Zecchin 2016 Table 2 λ_OS = 0.036 (1/month) = 0.001183 (1/day)
alfa_haz (Weibull shape) exp-1 = 2.004 Output_real_OS.lst FINAL TH2 = 2.00E+00; Table 2 α_OS = 1.99
e_sld0_haz (γ on SLD₀/70) 0.2084 Output_real_OS.lst FINAL TH3 = 2.08E-01; Table 2 γ_SLD0 = 0.189 (small rounding)
e_tsr_haz (γ on TSR(t)) 0.9036 Output_real_OS.lst FINAL TH4 = 9.04E-01; Table 2 γ_TSR(t) = 0.893
e_nwls_haz (γ on NWLS) 1.230 Output_real_OS.lst FINAL TH5 = 1.23E+00; Table 2 γ_NewLes(t) = 1.23
e_ecog_haz (γ on ECOG_GE1) 0.5162 Output_real_OS.lst FINAL TH6 = 5.16E-01; Table 2 γ_ECOG = 0.518
η on LAM (OMEGA(1,1)) 0 FIXED Output_real_OS.lst FINAL OMEGA(1,1) = 0.00E+00 (placeholder; no estimated IIV)

Virtual cohort

The bundle’s simulated dataset lives outside this package (dpastoor/ddmore_scraping/218/Simulated_OS.csv); the vignette builds a small virtual cohort programmatically, drawing the upstream IPP covariates (KG, KD0, KD1, IBASE) from log-normal distributions centred at the Zecchin 2016 SLD model’s typical values (modellib('Zecchin_2016_tumorovarian')) and the OMEGA variances reported in Output_real_SLD.lst. Treatment cycles are encoded as time-varying records updating AUC_CARBO and AUC_GEM at the start of each Q3W (21-day) cycle.

set.seed(20260506)

# Per-cycle Q3W carboplatin AUC (six cycles, day 0, 21, 42, 63, 84, 105)
cycle_starts <- c(0, 21, 42, 63, 84, 105)

# The bundle's subject 1 carboplatin per-cycle AUCs (used as the median
# value here; they are time-varying because actual administered AUCs
# fluctuate around the protocol target). Real bundle values are available
# in dpastoor/ddmore_scraping/218/Simulated_OS.csv (column AUC0).
median_auc_carbo <- 110

# Two treatment arms: carboplatin monotherapy and carboplatin + gemcitabine
arms <- c("carbo", "carbo+gem")

make_subject <- function(id, arm) {
  # Empirical-Bayes draw mimicking the upstream SLD model's posterior
  KG    <- exp(log(0.611)  + rnorm(1, 0, sqrt(1.72)))   # SLD lkg / OMEGA(1,1)
  shared_eta_kd <- rnorm(1, 0, sqrt(1.09))              # shared ETA(2) on KD0/KD1
  KD0   <- exp(log(0.0497) + shared_eta_kd)
  KD1   <- exp(log(0.0164) + shared_eta_kd)
  IBASE <- exp(log(0.0713) + rnorm(1, 0, sqrt(0.515)))  # in metres
  TUM_SLD <- IBASE * 1000 * exp(rnorm(1, 0, 0.1))       # measured SLD0 (mm); small noise around fitted baseline
  ECOG_GE1 <- as.integer(runif(1) < 0.4)                # 40% ECOG>=1 (typical Phase III ovarian cohort)

  # Per-cycle exposures (constant within cycle, reset at the next cycle).
  # Carboplatin given in every cycle; gemcitabine given only on the
  # combination arm.
  per_cycle <- tibble(
    time      = cycle_starts,
    AUC_CARBO = rlnorm(length(cycle_starts), meanlog = log(median_auc_carbo), sdlog = 0.15),
    AUC_GEM   = if (arm == "carbo+gem") rlnorm(length(cycle_starts), meanlog = log(2), sdlog = 0.15) else 0
  )

  # Observation grid (daily) covering 0 to 1095 days (~3 years).
  # NWLS = 0 throughout in this simplified cohort; the new-lesion submodel
  # is exercised separately in the "Sensitivity to covariates" section
  # below.
  obs_grid <- tibble(
    time      = c(0, 1, seq(7, 1095, by = 7)),
    AUC_CARBO = NA_real_,
    AUC_GEM   = NA_real_
  )

  ev <- bind_rows(per_cycle, obs_grid) |>
    arrange(time) |>
    fill(AUC_CARBO, AUC_GEM, .direction = "down") |>
    mutate(
      id        = id,
      evid      = 0L,
      amt       = 0,
      KG        = KG,
      KD0       = KD0,
      KD1       = KD1,
      IBASE     = IBASE,
      TUM_SLD   = TUM_SLD,
      ECOG_GE1  = ECOG_GE1,
      NWLS      = 0L,
      arm       = arm
    )
  ev
}

n_per_arm <- 50
events <- bind_rows(
  lapply(seq_len(n_per_arm), function(i) make_subject(id = i,                arm = "carbo")),
  lapply(seq_len(n_per_arm), function(i) make_subject(id = n_per_arm + i,    arm = "carbo+gem"))
)

stopifnot(!anyDuplicated(unique(events[, c("id", "time", "evid")])))
cat("Cohort: ", length(unique(events$id)), " subjects, ", nrow(events), " event rows\n", sep = "")
#> Cohort: 100 subjects, 16400 event rows

Simulation

sim <- rxode2::rxSolve(m, events = events, keep = c("arm")) |>
  as.data.frame()

Replicate published behaviour — typical-value Kaplan-Meier-style trajectory

Zecchin 2016 reports a Kaplan-Meier survival curve overlaid with the Weibull-hazard model fit (paper Figure 4 / Figure 5). Without the original patient-level data on disk, the vignette compares the model’s typical-value behaviour to the qualitative trajectory the paper reports: an approximately Weibull S(t) with shape 1.99, scale 0.036/month, modulated by the cohort’s covariate distribution.

sim |>
  group_by(time) |>
  summarise(
    median_sur = median(sur),
    q05        = quantile(sur, 0.05),
    q95        = quantile(sur, 0.95),
    .groups    = "drop"
  ) |>
  ggplot(aes(time, median_sur)) +
  geom_ribbon(aes(ymin = q05, ymax = q95), alpha = 0.25, fill = "steelblue") +
  geom_line(colour = "steelblue") +
  labs(
    x        = "Time (days)",
    y        = "Survival probability",
    title    = "Typical-value survival trajectory (median + 5-95% IQR over the virtual cohort)",
    caption  = "Compare against Kaplan-Meier overlay in Zecchin 2016 Figure 4 / Figure 5."
  ) +
  scale_y_continuous(limits = c(0, 1))

sim |>
  group_by(time, arm) |>
  summarise(median_sur = median(sur), .groups = "drop") |>
  ggplot(aes(time, median_sur, colour = arm)) +
  geom_line() +
  labs(
    x        = "Time (days)",
    y        = "Median survival probability",
    title    = "Median survival by treatment arm",
    colour   = "Arm",
    caption  = "Combination chemotherapy reduces tumour size faster (TSR more negative early), which feeds through e_tsr_haz to a slightly higher survival probability under the hazard model."
  ) +
  scale_y_continuous(limits = c(0, 1))

Mechanistic sanity checks (F.3)

The model is a TTE (time-to-event) survival model, not a PK / PD concentration model — PKNCA is not the right validation tool. references/verification-checklist.md § F.3 calls for typical-value hazard / survival trajectories to reproduce qualitative behaviour reported in the source. The four checks below exercise each covariate arm of the OS hazard.

F.3.1 — Hazard increases monotonically with time (Weibull α ≈ 2)

Weibull shape α > 1 means a hazard that increases with time. With α = 1.99 the cumulative hazard grows quadratically and the survival function is a right-shifted Weibull S(t) = exp(-(λt)^α).

ev_ref <- make_subject(id = 1L, arm = "carbo")
ev_ref$KG       <- 0.611
ev_ref$KD0      <- 0.0497
ev_ref$KD1     <- 0.0164
ev_ref$IBASE    <- 0.07
ev_ref$TUM_SLD  <- 70
ev_ref$ECOG_GE1 <- 0L
ev_ref$NWLS     <- 0L

sim_ref <- rxode2::rxSolve(rxode2::zeroRe(m), events = ev_ref) |> as.data.frame()
#> Warning: No omega parameters in the model
#> Warning: No sigma parameters in the model
ggplot(sim_ref, aes(time, hazard)) +
  geom_line(colour = "firebrick") +
  labs(x = "Time (days)", y = "Instantaneous hazard (1/day)",
       title = "Typical-subject hazard trajectory",
       caption = "Strictly increasing under Weibull α = 1.99 + non-decreasing covariate effects.")

stopifnot(all(diff(sim_ref$hazard[sim_ref$time > 0]) > -1e-12))

F.3.2 — Each covariate shifts the hazard in the published direction

Zecchin 2016 reports all four covariate effects as positive (γ > 0; Table 2 of the paper), i.e. higher baseline SLD, more positive TSR(t), appearance of new lesions, and ECOG ≥ 1 each increase the hazard. The plot below overlays survival trajectories for a baseline subject with one covariate toggled at a time.

make_alt <- function(label, mutator) {
  alt <- ev_ref
  alt$id <- as.integer(label_to_id(label))
  alt <- mutator(alt)
  alt$arm <- label
  alt
}

label_to_id <- function(x) match(x, c("baseline", "TUM_SLD = 140 mm",
                                       "ECOG_GE1 = 1", "NWLS turns 1 at day 200"))

scenarios <- bind_rows(
  make_alt("baseline",                function(d) d),
  make_alt("TUM_SLD = 140 mm",        function(d) {d$TUM_SLD <- 140; d}),
  make_alt("ECOG_GE1 = 1",            function(d) {d$ECOG_GE1 <- 1L; d}),
  make_alt("NWLS turns 1 at day 200", function(d) {d$NWLS <- as.integer(d$time >= 200); d})
)

sim_scen <- rxode2::rxSolve(rxode2::zeroRe(m), events = scenarios, keep = c("arm")) |>
  as.data.frame()
#> Warning: No omega parameters in the model
#> Warning: No sigma parameters in the model
ggplot(sim_scen, aes(time, sur, colour = arm)) +
  geom_line() +
  labs(x = "Time (days)", y = "Survival probability",
       title = "Typical-subject survival under one-at-a-time covariate shifts",
       colour = "Scenario") +
  scale_y_continuous(limits = c(0, 1))

final_sur <- sim_scen |>
  filter(time == max(time)) |>
  select(arm, sur)
print(final_sur)
#>                       arm        sur
#> 1                baseline 0.23440250
#> 2        TUM_SLD = 140 mm 0.16748610
#> 3            ECOG_GE1 = 1 0.08795882
#> 4 NWLS turns 1 at day 200 0.00790526

baseline_sur <- final_sur$sur[final_sur$arm == "baseline"]
non_baseline <- final_sur |> filter(arm != "baseline")
stopifnot(all(non_baseline$sur < baseline_sur))

F.3.3 — TSR clamp at week 12 (“WTS frozen at day 84”)

The source $DES block freezes the TSR effect on the hazard at the week-12 (84-day) value. The model’s auxiliary wts state is constructed to equal tsr for t ≤ 84 and to stay constant thereafter. The check plots tsr (the live ratio) against wts (the frozen-at-84 ratio) for a representative subject:

sim_one <- sim |> filter(id == 1)
ggplot(sim_one, aes(time)) +
  geom_line(aes(y = tsr, colour = "tsr (live)"),         linewidth = 0.7) +
  geom_line(aes(y = wts, colour = "wts (frozen at 84)"), linewidth = 0.7, linetype = "dashed") +
  geom_vline(xintercept = 84, colour = "grey60", linetype = "dotted") +
  labs(x = "Time (days)", y = "Tumour-size ratio",
       title = "TSR live vs. WTS frozen at week 12 (subject 1)",
       colour = NULL,
       caption = "wts = tsr for t <= 84, then stays constant. Source $DES IF(T.LE.84).")


# Algebraic check: |wts - tsr_at_84| ~ 0 for t < 84 and wts is constant for t > 84.
sim_one_tail <- sim_one |> filter(time > 84)
stopifnot(diff(range(sim_one_tail$wts)) < 1e-9)

F.3.4 — Self-consistency with the bundle’s simulated dataset

A full F.2-style self-consistency check would re-simulate the bundle’s shipped Simulated_OS.csv (336 subjects, 4780 records) under the nlmixr2lib model and compare against the bundle’s Output_simulated_OS.lst IPRED column. The bundle dataset is outside this package (in dpastoor/ddmore_scraping/218/) and not redistributed; exercising the check requires the user to point events at the bundle CSV. The cohort built above is a faithful smaller-scale analogue and the sanity checks F.3.1-F.3.3 above are the substitute exercised in this vignette.

Assumptions and deviations

  • Inline SLD ODE. The packaged model integrates the upstream SLD ODE inline (d/dt(tumorSize)) using subject-level KG, KD0, KD1, IBASE covariates from the empirical-Bayes posterior of the upstream Zecchin 2016 SLD model (DDMODEL00000217 / Zecchin_2016_tumorovarian). The two models are intended to be used together for OS forecasting conditional on a fitted SLD trajectory. Users running the OS model standalone must supply the four IPP covariates per subject (typically by first fitting the SLD model to per-subject SLD observations and carrying out the empirical-Bayes step).

  • ECOG dichotomization. The Output_real_OS.lst (the listing on the original real dataset) explicitly binarizes ECOG via IF(ECOG.GT.0) IECOG = 1, matching Zecchin 2016 Methods which dichotomizes ECOG to 0 vs ≥1 because of the small number of patients with ECOG > 1 at enrolment. The bundle’s Executable_OS.mod simplifies this to IECOG = ECOG, valid only because the bundle’s Simulated_OS.csv ships an already-binarized ECOG column with values in {0, 1}. The nlmixr2lib model uses the explicit canonical ECOG_GE1 covariate to make the dichotomization unambiguous; it is faithful to the publication’s Methods, to the run that produced the final estimates, and to the bundle’s simulated dataset.

  • NWLS time gate (.lst-only). The original Output_real_OS.lst gates INWLS on a TNWLS (time-of-new-lesion) column not shipped with the bundle’s simulated dataset (Executable_OS.mod uses a NEWIND-based LOCF carry-forward instead). The nlmixr2lib model follows the bundle’s executable: NWLS enters the hazard directly as the time-varying covariate value at each observation. When the dataset’s NWLS column is constructed as a 0/1 step that flips at the lesion-appearance time (the bundle convention), the two encodings are functionally equivalent.

  • No estimated IIV. The source $OMEGA is 0 FIX (placeholder slot with no estimated random effect). The OS sub-model has no IIV in the Zecchin 2016 publication. All inter-subject variability in this OS model derives from the IPP covariates (KG, KD0, KD1, IBASE) carried in from the upstream SLD fit and the time-varying / fixed covariates AUC_CARBO, AUC_GEM, NWLS, TUM_SLD, ECOG_GE1.

  • Lambda unit convention. Zecchin 2016 Table 2 reports λ_OS = 0.036 in 1/month (paper convention). The Output_real_OS.lst works in 1/day (per Executable_OS.mod $INPUT TIME, ;day); the FINAL TH1 = 1.18E-03 (1/day) ≡ 0.036/30.4375 ≈ 0.001183 (1/day). The nlmixr2lib model declares time in days and stores λ in 1/day to maintain numerical equivalence with the bundle.

  • No publication-PDF cross-check. The Zecchin 2016 BJCP PDF was not on disk for this extraction. Methods / Table 2 cross-checks were performed against the PMC HTML version (PMC5338128) via WebFetch.

  • Convention warnings. nlmixr2lib::checkModelConventions() flags three non-canonical compartment names (tumorSize, wts, cumHazard) and a non-mass/volume units$concentration value. These are intrinsic to a TTE model integrating an inline SLD ODE (tumorSize is paper-named tumour-burden length, wts and cumHazard are auxiliary states; the “concentration” output is a survival probability). The same tumorSize warning applies to the upstream Zecchin_2016_tumorovarian model and to every existing tumour-size model in inst/modeldb/therapeuticArea/oncology/.