|
| 1 | +# Copyright (c) 2023 Merck & Co., Inc., Rahway, NJ, USA and its affiliates. |
| 2 | +# All rights reserved. |
| 3 | +# |
| 4 | +# This file is part of the psm3mkv program. |
| 5 | +# |
| 6 | +# psm3mkv is free software: you can redistribute it and/or modify |
| 7 | +# it under the terms of the GNU General Public License as published by |
| 8 | +# the Free Software Foundation, either version 3 of the License, or |
| 9 | +# (at your option) any later version. |
| 10 | +# |
| 11 | +# This program is distributed in the hope that it will be useful, |
| 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | +# GNU General Public License for more details. |
| 15 | +# |
| 16 | +# You should have received a copy of the GNU General Public License |
| 17 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | +# |
| 19 | +# ================================================================== |
| 20 | +# Discretized restricted mean durations |
| 21 | +# discrmd.R |
| 22 | +# ===================================== |
| 23 | +# |
| 24 | + |
| 25 | +#' Discretized Restricted Mean Duration calculation for Partitioned Survival Model |
| 26 | +#' Calculate restricted mean duration (RMD) in PF, PD and OS states under a Partitioned Survival Model structure. |
| 27 | +#' @param dpam List of survival regressions for model endpoints. These must include time to progression (TTP) and pre-progression death (PPD). |
| 28 | +#' @param Ty Time duration over which to calculate (defaults to 10 years). Assumes input is in years, and patient-level data is recorded in weeks. |
| 29 | +#' @param discrate Discount rate (%) per year (defaults to zero). |
| 30 | +#' @param lifetable Optional. The lifetable must be a dataframe with columns named time and lx. The first entry of the time column must be zero. Data should be sorted in ascending order by time, and all times must be unique. |
| 31 | +#' @param timestep Optional, defaults to one (week). |
| 32 | +#' @return List containing: |
| 33 | +#' - pf: RMD in PF state |
| 34 | +#' - pd: RMD in PD state |
| 35 | +#' - os: RMD in either alive state |
| 36 | +#' @seealso [drmd_stm_cf()] [drmd_stm_cr()] |
| 37 | +#' @export |
| 38 | +#' @examples |
| 39 | +#' # Create dataset and fit survival models (splines) |
| 40 | +#' bosonc <- create_dummydata("flexbosms") |
| 41 | +#' fits <- fit_ends_mods_spl(bosonc) |
| 42 | +#' # Pick out best distribution according to min AIC |
| 43 | +#' params <- list( |
| 44 | +#' ppd = find_bestfit_spl(fits$ppd, "aic")$fit, |
| 45 | +#' ttp = find_bestfit_spl(fits$ttp, "aic")$fit, |
| 46 | +#' pfs = find_bestfit_spl(fits$pfs, "aic")$fit, |
| 47 | +#' os = find_bestfit_spl(fits$os, "aic")$fit, |
| 48 | +#' pps_cf = find_bestfit_spl(fits$pps_cf, "aic")$fit, |
| 49 | +#' pps_cr = find_bestfit_spl(fits$pps_cr, "aic")$fit |
| 50 | +#' ) |
| 51 | +#' drmd_psm(dpam=params) |
| 52 | +#' # Add a lifetable constraint |
| 53 | +#' ltable <- tibble::tibble(lttime=0:20, lx=1-lttime*0.05) |
| 54 | +#' drmd_psm(dpam=params, lifetable=ltable) |
| 55 | +drmd_psm <- function(dpam, Ty=10, discrate=0, lifetable=NA, timestep=1) { |
| 56 | + # Declare local variables |
| 57 | + Tw <- tvec <- pfprob <- osprob <- adjosprob <- adjfac <- adjprob <- vn <- NULL |
| 58 | + # Time horizon in weeks (ceiling) |
| 59 | + Tw <- convert_yrs2wks(Ty) |
| 60 | + # Create time vector, with half-cycle addition |
| 61 | + tvec <- timestep*(1:floor(Tw/timestep)) + timestep/2 |
| 62 | + # Membership probabilities with lifetable constraint |
| 63 | + pfprob <- prob_pf_psm(tvec, dpam) |
| 64 | + osprob <- prob_os_psm(tvec, dpam) |
| 65 | + adjosprob <- constrain_survprob(osprob, lifetable=lifetable, timevec=tvec) |
| 66 | + adjfac <- adjosprob/osprob |
| 67 | + adjfac[is.na(adjfac)] <- 1 |
| 68 | + adjpfprob <- pfprob * adjfac |
| 69 | + # Discount factor |
| 70 | + vn <- (1+discrate)^(-convert_wks2yrs(tvec+timestep/2)) |
| 71 | + # Calculate RMDs |
| 72 | + pf <- sum(adjpfprob*vn) * timestep |
| 73 | + os <- sum(adjosprob*vn) * timestep |
| 74 | + # Return values |
| 75 | + return(list(pf=pf, pd=os-pf, os=os)) |
| 76 | +} |
| 77 | + |
| 78 | +#' Discretized Restricted Mean Duration calculation for State Transition Model Clock Forward structure |
| 79 | +#' Calculate restricted mean duration (RMD) in PF, PD and OS states under a State Transition Model Clock Forward structure. |
| 80 | +#' @inherit drmd_psm params return |
| 81 | +#' @seealso [drmd_psm()] [drmd_stm_cr()] |
| 82 | +#' @export |
| 83 | +#' @examples |
| 84 | +#' # Create dataset and fit survival models (splines) |
| 85 | +#' bosonc <- create_dummydata("flexbosms") |
| 86 | +#' fits <- fit_ends_mods_spl(bosonc) |
| 87 | +#' # Pick out best distribution according to min AIC |
| 88 | +#' params <- list( |
| 89 | +#' ppd = find_bestfit_spl(fits$ppd, "aic")$fit, |
| 90 | +#' ttp = find_bestfit_spl(fits$ttp, "aic")$fit, |
| 91 | +#' pfs = find_bestfit_spl(fits$pfs, "aic")$fit, |
| 92 | +#' os = find_bestfit_spl(fits$os, "aic")$fit, |
| 93 | +#' pps_cf = find_bestfit_spl(fits$pps_cf, "aic")$fit, |
| 94 | +#' pps_cr = find_bestfit_spl(fits$pps_cr, "aic")$fit |
| 95 | +#' ) |
| 96 | +#' drmd_stm_cf(dpam=params) |
| 97 | +#' # Add a lifetable constraint |
| 98 | +#' ltable <- tibble::tibble(lttime=0:20, lx=1-lttime*0.05) |
| 99 | +#' drmd_stm_cf(dpam=params, lifetable=ltable) |
| 100 | +drmd_stm_cf <- function(dpam, Ty=10, discrate=0, lifetable=NA, timestep=1) { |
| 101 | + # Declare local variables |
| 102 | + Tw <- tvec <- ppd.ts <- ttp.ts <- sppd <- sttp <- sos <- NULL |
| 103 | + adjsppd <- adjos <- vn <- pf <- os <- NULL |
| 104 | + # Time horizon in weeks (ceiling) |
| 105 | + Tw <- convert_yrs2wks(Ty) |
| 106 | + # Create time vector, with half-cycle addition |
| 107 | + tvec <- timestep*(1:floor(Tw/timestep)) + timestep/2 |
| 108 | + # Pull out type and spec for PPD and TTP |
| 109 | + ppd.ts <- convert_fit2spec(dpam$ppd) |
| 110 | + ttp.ts <- convert_fit2spec(dpam$ttp) |
| 111 | + # Calculate S_PPD, S_TTP and S_OS |
| 112 | + sppd <- tvec |> purrr::map_dbl(~calc_surv(.x, ppd.ts$type, ppd.ts$spec)) |
| 113 | + sttp <- tvec |> purrr::map_dbl(~calc_surv(.x, ttp.ts$type, ttp.ts$spec)) |
| 114 | + # Next line is the difference with STM-CR |
| 115 | + sos <- prob_os_stm_cf(tvec, dpam) |
| 116 | + # Apply constraints to S_PPD and S_OS |
| 117 | + adjsppd <- constrain_survprob(sppd, lifetable=lifetable, timevec=tvec) |
| 118 | + adjos <- constrain_survprob(sos, lifetable=lifetable, timevec=tvec) |
| 119 | + # Discount factor |
| 120 | + vn <- (1+discrate)^(-convert_wks2yrs(tvec+timestep/2)) |
| 121 | + # Calculate RMDs |
| 122 | + pf <- sum(sttp*adjsppd*vn) * timestep |
| 123 | + os <- sum(adjos*vn) * timestep |
| 124 | + # Return values |
| 125 | + return(list(pf=pf, pd=os-pf, os=os)) |
| 126 | +} |
| 127 | + |
| 128 | +#' Discretized Restricted Mean Duration calculation for State Transition Model Clock Reset structure |
| 129 | +#' Calculate restricted mean duration (RMD) in PF, PD and OS states under a State Transition Model Clock Reset structure. |
| 130 | +#' @inherit drmd_psm params return |
| 131 | +#' @seealso [drmd_stm_cf()] [drmd_psm()] |
| 132 | +#' @export |
| 133 | +#' @examples |
| 134 | +#' # Create dataset and fit survival models (splines) |
| 135 | +#' bosonc <- create_dummydata("flexbosms") |
| 136 | +#' fits <- fit_ends_mods_spl(bosonc) |
| 137 | +#' # Pick out best distribution according to min AIC |
| 138 | +#' params <- list( |
| 139 | +#' ppd = find_bestfit_spl(fits$ppd, "aic")$fit, |
| 140 | +#' ttp = find_bestfit_spl(fits$ttp, "aic")$fit, |
| 141 | +#' pfs = find_bestfit_spl(fits$pfs, "aic")$fit, |
| 142 | +#' os = find_bestfit_spl(fits$os, "aic")$fit, |
| 143 | +#' pps_cf = find_bestfit_spl(fits$pps_cf, "aic")$fit, |
| 144 | +#' pps_cr = find_bestfit_spl(fits$pps_cr, "aic")$fit |
| 145 | +#' ) |
| 146 | +#' drmd_stm_cr(dpam=params) |
| 147 | +#' # Add a lifetable constraint |
| 148 | +#' ltable <- tibble::tibble(lttime=0:20, lx=1-lttime*0.05) |
| 149 | +#' drmd_stm_cr(dpam=params, lifetable=ltable) |
| 150 | +drmd_stm_cr <- function(dpam, Ty=10, discrate=0, lifetable=NA, timestep=1) { |
| 151 | + # Declare local variables |
| 152 | + Tw <- tvec <- ppd.ts <- ttp.ts <- sppd <- sttp <- sos <- NULL |
| 153 | + adjsppd <- adjos <- vn <- pf <- os <- NULL |
| 154 | + # Time horizon in weeks (ceiling) |
| 155 | + Tw <- convert_yrs2wks(Ty) |
| 156 | + # Create time vector, with half-cycle addition |
| 157 | + tvec <- timestep*(1:floor(Tw/timestep)) + timestep/2 |
| 158 | + # Pull out type and spec for PPD and TTP |
| 159 | + ppd.ts <- convert_fit2spec(dpam$ppd) |
| 160 | + ttp.ts <- convert_fit2spec(dpam$ttp) |
| 161 | + # Calculate S_PPD, S_TTP and S_OS |
| 162 | + sppd <- tvec |> purrr::map_dbl(~calc_surv(.x, ppd.ts$type, ppd.ts$spec)) |
| 163 | + sttp <- tvec |> purrr::map_dbl(~calc_surv(.x, ttp.ts$type, ttp.ts$spec)) |
| 164 | + # Next line is the difference with STM-CF |
| 165 | + sos <- prob_os_stm_cr(tvec, dpam) |
| 166 | + # Apply constraints to S_PPD and S_OS |
| 167 | + adjsppd <- constrain_survprob(sppd, lifetable=lifetable, timevec=tvec) |
| 168 | + adjos <- constrain_survprob(sos, lifetable=lifetable, timevec=tvec) |
| 169 | + # Discount factor |
| 170 | + vn <- (1+discrate)^(-convert_wks2yrs(tvec+timestep/2)) |
| 171 | + # Calculate RMDs |
| 172 | + pf <- sum(sttp*adjsppd*vn) * timestep |
| 173 | + os <- sum(adjos*vn) * timestep |
| 174 | + # Return values |
| 175 | + return(list(pf=pf, pd=os-pf, os=os)) |
| 176 | +} |
| 177 | + |
| 178 | + |
0 commit comments