| 1 |
#' Event Times Distributed as Sum of Weibull |
|
| 2 |
#' |
|
| 3 |
#' This returns event times with a distribution resulting from the sum of two Weibull distributed random variables |
|
| 4 |
#' using the inversion method. |
|
| 5 |
#' |
|
| 6 |
#' @param U (`numeric`)\cr uniformly distributed random variables. |
|
| 7 |
#' @param haz1 (positive `number`)\cr first summand (constant hazard). |
|
| 8 |
#' @param haz2 (positive `number`)\cr second summand (constant hazard). |
|
| 9 |
#' @param p1 (positive `number`)\cr rate parameter of Weibull distribution for `haz1`. |
|
| 10 |
#' @param p2 (positive `number`)\cr rate parameter of Weibull distribution for `haz2`. |
|
| 11 |
#' @param entry (`numeric`)\cr the entry times in the current state. |
|
| 12 |
#' |
|
| 13 |
#' @return This returns a vector with event times. |
|
| 14 |
#' @export |
|
| 15 |
#' |
|
| 16 |
#' @examples |
|
| 17 |
#' getWaitTimeSum(U = c(0.4, 0.5), haz1 = 0.8, haz2 = 1, p1 = 1.1, p2 = 1.5, entry = c(0, 0)) |
|
| 18 |
getWaitTimeSum <- function(U, haz1, haz2, p1, p2, entry) {
|
|
| 19 | 6232x |
assert_numeric(U, lower = 0, upper = 1, any.missing = FALSE, all.missing = FALSE) |
| 20 | 6232x |
assert_positive_number(haz1, zero_ok = TRUE) |
| 21 | 6232x |
assert_positive_number(haz2, zero_ok = TRUE) |
| 22 | 6232x |
assert_positive_number(p1) |
| 23 | 6232x |
assert_positive_number(p2) |
| 24 | 6232x |
assert_numeric(entry, lower = 0, len = length(U), any.missing = FALSE, all.missing = FALSE) |
| 25 | ||
| 26 | 6232x |
N <- length(U) |
| 27 |
# Exponential distributed survival times. |
|
| 28 | 6232x |
if (p1 == 1 && p2 == 1) {
|
| 29 | 2187x |
return(-log(1 - U) / (haz1 + haz2)) |
| 30 |
} |
|
| 31 |
# Weibull distributed survival times. |
|
| 32 | 4045x |
temp <- function(x, y, t0) {
|
| 33 | 15888829x |
return(haz1 * (x + t0)^p1 - haz1 * t0^p1 - haz2 * t0^p2 + haz2 * (x + t0)^p2 + y) |
| 34 |
} |
|
| 35 | 4045x |
stime <- NULL |
| 36 | 4045x |
i <- 1 |
| 37 | 4045x |
while (length(stime) < N) {
|
| 38 | 696412x |
u <- U[i] |
| 39 | 696412x |
t0 <- entry[i] |
| 40 | 696412x |
if (temp(0, log(1 - u), t0) * temp(10000, log(1 - u), t0) < 0) {
|
| 41 | 696412x |
res <- stats::uniroot(temp, c(0, 10000), tol = 10^-16, y = log(1 - u), t0 = t0) |
| 42 | 696412x |
stime[i] <- res$root |
| 43 | 696412x |
i <- i + 1 |
| 44 | 696412x |
if (res$root == 0) {
|
| 45 | ! |
warning("uniroot: accuracy (tol=10^-16) is not enough.")
|
| 46 |
} |
|
| 47 |
} else {
|
|
| 48 | ! |
warning("uniroot: Values at interval endpoints (interval=c(0,10000)) are not of opposite signs. \n")
|
| 49 |
} |
|
| 50 |
} |
|
| 51 | 4045x |
stime |
| 52 |
} |
| 1 |
#' Time-point by which a specified number of events occurred. |
|
| 2 |
#' |
|
| 3 |
#' This returns the study time-point by which a specified number of events (PFS or OS) occurred. |
|
| 4 |
#' |
|
| 5 |
#' @param data (`data.frame`)\cr illness-death data set in `1rowPatient` format. |
|
| 6 |
#' @param eventNum (`int`)\cr number of events. |
|
| 7 |
#' @param typeEvent (`string`)\cr type of event. Possible values are `PFS` and `OS`. |
|
| 8 |
#' @param byArm (`logical`)\cr if `TRUE` time-point per treatment arm, else joint evaluation |
|
| 9 |
#' of treatment arms. |
|
| 10 |
#' |
|
| 11 |
#' @return This returns the time-point by which `eventNum` of `typeEvent`-events occurred. |
|
| 12 |
#' @export |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' transition1 <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 0.8, p02 = 0.9, p12 = 1) |
|
| 16 |
#' transition2 <- weibull_transition(h01 = 1, h02 = 1.3, h12 = 1.7, p01 = 1.1, p02 = 0.9, p12 = 1.1) |
|
| 17 |
#' |
|
| 18 |
#' simStudy <- getOneClinicalTrial( |
|
| 19 |
#' nPat = c(20, 20), transitionByArm = list(transition1, transition2), |
|
| 20 |
#' dropout = list(rate = 0.3, time = 10), |
|
| 21 |
#' accrual = list(param = "time", value = 0) |
|
| 22 |
#' ) |
|
| 23 |
#' simStudyWide <- getDatasetWideFormat(simStudy) |
|
| 24 |
#' getTimePoint(simStudyWide, eventNum = 10, typeEvent = "OS", byArm = FALSE) |
|
| 25 |
getTimePoint <- function(data, eventNum, typeEvent, byArm = FALSE) {
|
|
| 26 | 112x |
assert_data_frame(data, ncols = 11) |
| 27 | 112x |
assert_int(eventNum, lower = 1L) |
| 28 | 112x |
assert_choice(typeEvent, c("OS", "PFS"))
|
| 29 | 112x |
assert_logical(byArm) |
| 30 | ||
| 31 | 112x |
if (!byArm) {
|
| 32 | 110x |
data$trt <- "all" |
| 33 |
} |
|
| 34 | 112x |
byVar <- unique(data$trt) |
| 35 | ||
| 36 | 112x |
if (typeEvent == "OS") {
|
| 37 | 56x |
data$event <- data$OSevent |
| 38 | 56x |
data$time <- data$OStimeCal |
| 39 | 56x |
} else if (typeEvent == "PFS") {
|
| 40 | 56x |
data$event <- data$PFSevent |
| 41 | 56x |
data$time <- data$PFStimeCal |
| 42 |
} |
|
| 43 | ||
| 44 | 112x |
sapply(byVar, function(trt) {
|
| 45 | 114x |
dataTemp <- data[data$trt == trt, ] |
| 46 | 114x |
sortedTimes <- sort(dataTemp$time[dataTemp$event == 1]) |
| 47 | 114x |
if (eventNum > length(sortedTimes)) {
|
| 48 | ! |
warning("Less events occur till EOS than specified \n")
|
| 49 |
} |
|
| 50 | 114x |
timePoint <- sortedTimes[eventNum] |
| 51 |
}) |
|
| 52 |
} |
|
| 53 | ||
| 54 |
#' Helper function for `censoringByNumberEvents` |
|
| 55 |
#' |
|
| 56 |
#' @param time (`numeric`) \cr event times. |
|
| 57 |
#' @param event (`numeric`)\cr event indicator. |
|
| 58 |
#' @param data (`data.frame`)\cr data frame including patient id `id`, recruiting time `recruitTime` |
|
| 59 |
#' and individual censoring time `censTimeInd`. |
|
| 60 |
#' |
|
| 61 |
#' @return This function returns a data frame with columns: |
|
| 62 |
#' event time, censoring indicator, event indicator and event time |
|
| 63 |
#' in calendar time. |
|
| 64 |
#' @export |
|
| 65 |
#' |
|
| 66 |
#' @examples |
|
| 67 |
#' transition1 <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 0.8, p02 = 0.9, p12 = 1) |
|
| 68 |
#' transition2 <- weibull_transition(h01 = 1, h02 = 1.3, h12 = 1.7, p01 = 1.1, p02 = 0.9, p12 = 1.1) |
|
| 69 |
#' |
|
| 70 |
#' simStudy <- getOneClinicalTrial( |
|
| 71 |
#' nPat = c(20, 20), transitionByArm = list(transition1, transition2), |
|
| 72 |
#' dropout = list(rate = 0.3, time = 10), |
|
| 73 |
#' accrual = list(param = "time", value = 7) |
|
| 74 |
#' ) |
|
| 75 |
#' simStudyWide <- getDatasetWideFormat(simStudy) |
|
| 76 |
#' simStudyWide$censTimeInd <- 5 - simStudyWide$recruitTime |
|
| 77 |
#' NotRecruited <- simStudyWide$id[simStudyWide$censTimeInd < 0] |
|
| 78 |
#' censoredData <- simStudyWide[!(simStudyWide$id %in% NotRecruited), ] |
|
| 79 |
#' getCensoredData(time = censoredData$OStime, event = censoredData$OSevent, data = censoredData) |
|
| 80 |
getCensoredData <- function(time, event, data) {
|
|
| 81 | 217x |
assert_numeric(time, lower = 0) |
| 82 | 217x |
assert_numeric(event, lower = 0, upper = 1) |
| 83 | 217x |
assert_data_frame(data) |
| 84 | ||
| 85 |
# Event time censored? |
|
| 86 | 217x |
epsilon <- 1e-10 |
| 87 | 217x |
Censored <- data$id[(time - epsilon) > data$censTimeInd] |
| 88 |
# keep minimum of censoring and event time. epsilon) |
|
| 89 | 217x |
Censoredtime <- pmin(time, data$censTimeInd) |
| 90 |
# adjust event and censoring indicators. |
|
| 91 | 217x |
CensoredEvent <- ifelse(data$id %in% Censored, 0, event) |
| 92 | 217x |
Censored <- abs(1 - CensoredEvent) |
| 93 |
# calculate corresponding calendar times. |
|
| 94 | 217x |
timeCal <- Censoredtime + data$recruitTime |
| 95 | ||
| 96 | 217x |
data.frame( |
| 97 | 217x |
time = Censoredtime, |
| 98 | 217x |
Censored = Censored, |
| 99 | 217x |
event = CensoredEvent, |
| 100 | 217x |
timeCal = timeCal |
| 101 |
) |
|
| 102 |
} |
|
| 103 | ||
| 104 |
#' Event-driven censoring. |
|
| 105 |
#' |
|
| 106 |
#' This function censors a study after a pre-specified number of events occurred. |
|
| 107 |
#' |
|
| 108 |
#' @param data (`data.frame`)\cr illness-death data set in `1rowPatient` format. |
|
| 109 |
#' @param eventNum (`int`)\cr number of events. |
|
| 110 |
#' @param typeEvent (`string`)\cr type of event. Possible values are `PFS` and `OS`. |
|
| 111 |
#' |
|
| 112 |
#' @return This function returns a data set that is censored after `eventNum` of |
|
| 113 |
#' `typeEvent`-events occurred. |
|
| 114 |
#' @export |
|
| 115 |
#' |
|
| 116 |
#' @examples |
|
| 117 |
#' transition1 <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 0.8, p02 = 0.9, p12 = 1) |
|
| 118 |
#' transition2 <- weibull_transition(h01 = 1, h02 = 1.3, h12 = 1.7, p01 = 1.1, p02 = 0.9, p12 = 1.1) |
|
| 119 |
#' |
|
| 120 |
#' simStudy <- getOneClinicalTrial( |
|
| 121 |
#' nPat = c(20, 20), transitionByArm = list(transition1, transition2), |
|
| 122 |
#' dropout = list(rate = 0.3, time = 10), |
|
| 123 |
#' accrual = list(param = "time", value = 7) |
|
| 124 |
#' ) |
|
| 125 |
#' simStudyWide <- getDatasetWideFormat(simStudy) |
|
| 126 |
#' censoringByNumberEvents(data = simStudyWide, eventNum = 20, typeEvent = "PFS") |
|
| 127 |
censoringByNumberEvents <- function(data, eventNum, typeEvent) {
|
|
| 128 | 108x |
assert_data_frame(data, ncols = 11) |
| 129 | 108x |
assert_int(eventNum, lower = 1L) |
| 130 | 108x |
assert_choice(typeEvent, c("OS", "PFS"))
|
| 131 | ||
| 132 |
# first step get timePoint (calendar time) for censoring. |
|
| 133 | 108x |
censTime <- getTimePoint(data, eventNum, typeEvent, byArm = FALSE) |
| 134 | ||
| 135 |
# censoring time at individual time scale. |
|
| 136 | 108x |
data$censTimeInd <- censTime - data$recruitTime |
| 137 |
# patients that are not yet recruited at censoring time has to be deleted. |
|
| 138 | 108x |
NotRecruited <- data$id[data$censTimeInd < 0] |
| 139 | 108x |
censoredData <- data[!(data$id %in% NotRecruited), ] |
| 140 | ||
| 141 | 108x |
OSdata <- getCensoredData(time = censoredData$OStime, event = censoredData$OSevent, data = censoredData) |
| 142 | 108x |
PFSdata <- getCensoredData(time = censoredData$PFStime, event = censoredData$PFSevent, data = censoredData) |
| 143 | ||
| 144 | 108x |
data.frame( |
| 145 | 108x |
id = censoredData$id, trt = censoredData$trt, |
| 146 | 108x |
PFStime = PFSdata$time, PFSevent = PFSdata$event, |
| 147 | 108x |
OStime = OSdata$time, CensoredOS = OSdata$Censored, OSevent = OSdata$event, |
| 148 | 108x |
recruitTime = censoredData$recruitTime, OStimeCal = OSdata$timeCal, PFStimeCal = PFSdata$timeCal |
| 149 |
) |
|
| 150 |
} |
|
| 151 | ||
| 152 |
#' Number of recruited/censored/ongoing Patients. |
|
| 153 |
#' |
|
| 154 |
#' |
|
| 155 |
#' @param data (`data.frame`)\cr illness-death data set in `1rowPatient` format. |
|
| 156 |
#' @param t (`numeric`)\cr study time-point. |
|
| 157 |
#' |
|
| 158 |
#' @return This function returns number of recruited patients, |
|
| 159 |
#' number of censored and number of patients under observations. |
|
| 160 |
#' @export |
|
| 161 |
#' |
|
| 162 |
#' @examples |
|
| 163 |
#' transition1 <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 0.8, p02 = 0.9, p12 = 1) |
|
| 164 |
#' transition2 <- weibull_transition(h01 = 1, h02 = 1.3, h12 = 1.7, p01 = 1.1, p02 = 0.9, p12 = 1.1) |
|
| 165 |
#' |
|
| 166 |
#' simStudy <- getOneClinicalTrial( |
|
| 167 |
#' nPat = c(20, 20), transitionByArm = list(transition1, transition2), |
|
| 168 |
#' dropout = list(rate = 0.6, time = 10), |
|
| 169 |
#' accrual = list(param = "time", value = 0) |
|
| 170 |
#' ) |
|
| 171 |
#' simStudyWide <- getDatasetWideFormat(simStudy) |
|
| 172 |
#' getEventsAll(data = simStudyWide, t = 1.5) |
|
| 173 |
getEventsAll <- function(data, t) {
|
|
| 174 | 3x |
assert_data_frame(data, ncols = 11) |
| 175 | 3x |
assert_positive_number(t, zero_ok = FALSE) |
| 176 | ||
| 177 | 3x |
allRecruited <- data$id[data$recruitTime <= t] |
| 178 | 3x |
allCensored <- data$id[(data$OStime + data$recruitTime) <= t & data$OSevent == 0] |
| 179 | 3x |
allDeath <- data$id[(data$OStime + data$recruitTime) <= t & data$OSevent == 1] |
| 180 |
## recruited, not death, not censored. |
|
| 181 | 3x |
allUnderObs <- data$id[data$id %in% allRecruited & !(data$id %in% allCensored) & |
| 182 | 3x |
!(data$id %in% allDeath)] |
| 183 | 3x |
numRecruited <- length(unique(allRecruited)) |
| 184 | 3x |
numCensored <- length(unique(allCensored)) |
| 185 | 3x |
numUnderObs <- length(unique(allUnderObs)) |
| 186 | ||
| 187 | 3x |
c( |
| 188 | 3x |
Recruited = numRecruited, |
| 189 | 3x |
Censored = numCensored, |
| 190 | 3x |
UnderObs = numUnderObs |
| 191 |
) |
|
| 192 |
} |
|
| 193 | ||
| 194 |
#' Helper Function for `trackEventsPerTrial` |
|
| 195 |
#' |
|
| 196 |
#' @param event (`numeric`)\cr event indicator. |
|
| 197 |
#' @param time (`numeric`) \cr event times. |
|
| 198 |
#' @param t (`numeric`)\cr study time-point. |
|
| 199 |
#' |
|
| 200 |
#' @return This function returns the number of events occurred until time t. |
|
| 201 |
#' @export |
|
| 202 |
#' |
|
| 203 |
#' @examples |
|
| 204 |
#' event <- c(0, 1, 1, 1, 0) |
|
| 205 |
#' time <- c(3, 3.4, 5, 6, 5.5) |
|
| 206 |
#' getNumberEvents(event = event, time = time, t = 5) |
|
| 207 |
getNumberEvents <- function(event, time, t) {
|
|
| 208 | 5x |
assert_numeric(event, lower = 0, upper = 1) |
| 209 | 5x |
assert_numeric(time, lower = 0) |
| 210 | 5x |
assert_number(t, lower = 0) |
| 211 | ||
| 212 | 5x |
sum(event[time <= t]) |
| 213 |
} |
|
| 214 | ||
| 215 | ||
| 216 |
#' Event tracking in an oncology trial. |
|
| 217 |
#' |
|
| 218 |
#' @param data (`data.frame`)\cr illness-death data set in `1rowPatient` format. |
|
| 219 |
#' @param timeP (`numeric`)\cr vector of study time-points. |
|
| 220 |
#' @param byArm (`logical`)\cr if `TRUE` time-point per treatment arm, else joint evaluation of treatment arms. |
|
| 221 |
#' |
|
| 222 |
#' @return This function returns a data frame including number of PFS events, number of OS events, |
|
| 223 |
#' number of recruited patients, number of censored patients and number of ongoing patients at `timeP`. |
|
| 224 |
#' |
|
| 225 |
#' @export |
|
| 226 |
#' |
|
| 227 |
#' @examples |
|
| 228 |
#' transition1 <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 0.8, p02 = 0.9, p12 = 1) |
|
| 229 |
#' transition2 <- weibull_transition(h01 = 1, h02 = 1.3, h12 = 1.7, p01 = 1.1, p02 = 0.9, p12 = 1.1) |
|
| 230 |
#' |
|
| 231 |
#' simStudy <- getOneClinicalTrial( |
|
| 232 |
#' nPat = c(20, 20), transitionByArm = list(transition1, transition2), |
|
| 233 |
#' dropout = list(rate = 0.3, time = 10), |
|
| 234 |
#' accrual = list(param = "time", value = 0) |
|
| 235 |
#' ) |
|
| 236 |
#' simStudyWide <- getDatasetWideFormat(simStudy) |
|
| 237 |
#' trackEventsPerTrial(data = simStudyWide, timeP = 1.5, byArm = FALSE) |
|
| 238 |
trackEventsPerTrial <- function(data, timeP, byArm = FALSE) {
|
|
| 239 | 1x |
assert_data_frame(data, ncols = 11) |
| 240 | 1x |
assert_numeric(timeP, lower = 0) |
| 241 | 1x |
assert_logical(byArm) |
| 242 | ||
| 243 | 1x |
if (!byArm) {
|
| 244 | ! |
data$trt <- "all" |
| 245 |
} |
|
| 246 | 1x |
byVar <- unique(data$trt) |
| 247 | 1x |
allNumbers <- lapply(byVar, function(j) {
|
| 248 | 2x |
datTemp <- data[data$trt == j, ] |
| 249 | 2x |
eventsPFS <- sapply(timeP, getNumberEvents, event = datTemp$PFSevent, time = datTemp$PFStimeCal) |
| 250 | 2x |
eventsOS <- sapply(timeP, getNumberEvents, event = datTemp$OSevent, time = datTemp$OStimeCal) |
| 251 | 2x |
eventsTrial <- sapply(timeP, getEventsAll, data = datTemp) |
| 252 | ||
| 253 | 2x |
allNumbers <- rbind(eventsPFS, eventsOS, eventsTrial) |
| 254 | 2x |
rownames(allNumbers) <- c("PFS", "OS", "Recruited", "Censored", "Ongoing")
|
| 255 | 2x |
colnames(allNumbers) <- paste0("Timepoint: ", round(timeP, 2))
|
| 256 | 2x |
allNumbers |
| 257 |
}) |
|
| 258 | 1x |
names(allNumbers) <- byVar |
| 259 | 1x |
allNumbers |
| 260 |
} |
| 1 |
# survPFS ---- |
|
| 2 | ||
| 3 |
#' PFS Survival Function for Different Transition Models |
|
| 4 |
#' |
|
| 5 |
#' @param transition (`TransitionParameters`)\cr |
|
| 6 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 7 |
#' @param t (`numeric`)\cr time at which the value of the PFS survival function is to be computed. |
|
| 8 |
#' |
|
| 9 |
#' @return The value of the survival function for the specified transition and time. |
|
| 10 |
#' @export |
|
| 11 |
#' |
|
| 12 |
#' @examples |
|
| 13 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 14 |
#' survPFS(transition, 0.4) |
|
| 15 |
survPFS <- function(transition, t) {
|
|
| 16 | 697x |
UseMethod("survPFS")
|
| 17 |
} |
|
| 18 | ||
| 19 |
#' @describeIn survPFS Survival Function for an exponential transition model. |
|
| 20 |
#' @export |
|
| 21 |
#' |
|
| 22 |
#' @examples |
|
| 23 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 24 |
#' survPFS(transition, 0.4) |
|
| 25 |
survPFS.ExponentialTransition <- function(transition, t) {
|
|
| 26 | 533x |
ExpSurvPFS(t = t, h01 = transition$hazards$h01, h02 = transition$hazards$h02) |
| 27 |
} |
|
| 28 | ||
| 29 |
#' @describeIn survPFS Survival Function for a Weibull transition model. |
|
| 30 |
#' @export |
|
| 31 |
#' |
|
| 32 |
#' @examples |
|
| 33 |
#' transition <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 2, p02 = 2.5, p12 = 3) |
|
| 34 |
#' survPFS(transition, 0.4) |
|
| 35 |
survPFS.WeibullTransition <- function(transition, t) {
|
|
| 36 | 153x |
WeibSurvPFS( |
| 37 | 153x |
t = t, h01 = transition$hazards$h01, h02 = transition$hazards$h02, |
| 38 | 153x |
p01 = transition$weibull_rates$p01, p02 = transition$weibull_rates$p02 |
| 39 |
) |
|
| 40 |
} |
|
| 41 | ||
| 42 |
#' @describeIn survPFS Survival Function for a piecewise constant transition model. |
|
| 43 |
#' @export |
|
| 44 |
#' |
|
| 45 |
#' @examples |
|
| 46 |
#' transition <- piecewise_exponential( |
|
| 47 |
#' h01 = c(1, 1, 1), h02 = c(1.5, 0.5, 1), h12 = c(1, 1, 1), |
|
| 48 |
#' pw01 = c(0, 3, 8), pw02 = c(0, 6, 7), pw12 = c(0, 8, 9) |
|
| 49 |
#' ) |
|
| 50 |
#' survPFS(transition, 0.4) |
|
| 51 |
survPFS.PWCTransition <- function(transition, t) {
|
|
| 52 | 11x |
PWCsurvPFS(t, |
| 53 | 11x |
h01 = transition$hazards$h01, h02 = transition$hazards$h02, |
| 54 | 11x |
pw01 = transition$intervals$pw01, pw02 = transition$intervals$pw02 |
| 55 |
) |
|
| 56 |
} |
|
| 57 | ||
| 58 |
# survOS ---- |
|
| 59 | ||
| 60 |
#' OS Survival Function for Different Transition Models |
|
| 61 |
#' |
|
| 62 |
#' @param transition (`TransitionParameters`)\cr |
|
| 63 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 64 |
#' @param t (`numeric`)\cr time at which the value of the OS survival function is to be computed. |
|
| 65 |
#' |
|
| 66 |
#' @return The value of the survival function for the specified transition and time. |
|
| 67 |
#' @export |
|
| 68 |
#' |
|
| 69 |
#' @examples |
|
| 70 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 71 |
#' survOS(transition, 0.4) |
|
| 72 |
survOS <- function(transition, t) {
|
|
| 73 | 25x |
UseMethod("survOS")
|
| 74 |
} |
|
| 75 | ||
| 76 |
#' @describeIn survOS Survival Function for an exponential transition model. |
|
| 77 |
#' @export |
|
| 78 |
#' |
|
| 79 |
#' @examples |
|
| 80 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 81 |
#' survOS(transition, 0.4) |
|
| 82 |
survOS.ExponentialTransition <- function(transition, t) {
|
|
| 83 | 13x |
ExpSurvOS( |
| 84 | 13x |
t = t, h01 = transition$hazards$h01, h02 = transition$hazards$h02, |
| 85 | 13x |
h12 = transition$hazards$h12 |
| 86 |
) |
|
| 87 |
} |
|
| 88 | ||
| 89 |
#' @describeIn survOS Survival Function for a Weibull transition model. |
|
| 90 |
#' @export |
|
| 91 |
#' |
|
| 92 |
#' @examples |
|
| 93 |
#' transition <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 2, p02 = 2.5, p12 = 3) |
|
| 94 |
#' survOS(transition, 0.4) |
|
| 95 |
survOS.WeibullTransition <- function(transition, t) {
|
|
| 96 | 11x |
WeibSurvOS( |
| 97 | 11x |
t = t, h01 = transition$hazards$h01, h02 = transition$hazards$h02, |
| 98 | 11x |
h12 = transition$hazards$h12, p01 = transition$weibull_rates$p01, |
| 99 | 11x |
p02 = transition$weibull_rates$p02, p12 = transition$weibull_rates$p12 |
| 100 |
) |
|
| 101 |
} |
|
| 102 | ||
| 103 |
#' @describeIn survOS Survival Function for a piecewise constant transition model. |
|
| 104 |
#' @export |
|
| 105 |
#' |
|
| 106 |
#' @examples |
|
| 107 |
#' transition <- piecewise_exponential( |
|
| 108 |
#' h01 = c(1, 1, 1), h02 = c(1.5, 0.5, 1), h12 = c(1, 1, 1), |
|
| 109 |
#' pw01 = c(0, 3, 8), pw02 = c(0, 6, 7), pw12 = c(0, 8, 9) |
|
| 110 |
#' ) |
|
| 111 |
#' survOS(transition, 0.4) |
|
| 112 |
survOS.PWCTransition <- function(transition, t) {
|
|
| 113 | 1x |
PWCsurvOS( |
| 114 | 1x |
t = t, h01 = transition$hazards$h01, h02 = transition$hazards$h02, |
| 115 | 1x |
h12 = transition$hazards$h12, pw01 = transition$intervals$pw01, |
| 116 | 1x |
pw02 = transition$intervals$pw02, pw12 = transition$intervals$pw12 |
| 117 |
) |
|
| 118 |
} |
|
| 119 | ||
| 120 |
# expval ---- |
|
| 121 | ||
| 122 |
#' Helper Function for Computing E(PFS^2) |
|
| 123 |
#' |
|
| 124 |
#' @param x (`numeric`)\cr variable of integration. |
|
| 125 |
#' @param transition (`TransitionParameters`)\cr |
|
| 126 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 127 |
#' |
|
| 128 |
#' @return Numeric results of the integrand used to calculate E(PFS^2). |
|
| 129 |
#' @export |
|
| 130 |
#' |
|
| 131 |
#' @examples |
|
| 132 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 133 |
#' expvalPFSInteg(0.4, transition) |
|
| 134 |
expvalPFSInteg <- function(x, transition) {
|
|
| 135 | 9x |
x * survPFS(transition, x) |
| 136 |
} |
|
| 137 | ||
| 138 |
#' Helper Function for Computing E(OS^2) |
|
| 139 |
#' |
|
| 140 |
#' @param x (`numeric`)\cr variable of integration. |
|
| 141 |
#' @param transition (`TransitionParameters`)\cr |
|
| 142 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 143 |
#' |
|
| 144 |
#' @return Numeric results of the integrand used to calculate E(OS^2). |
|
| 145 |
#' @export |
|
| 146 |
#' |
|
| 147 |
#' @examples |
|
| 148 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 149 |
#' expvalOSInteg(0.4, transition) |
|
| 150 |
expvalOSInteg <- function(x, transition) {
|
|
| 151 | 11x |
x * survOS(transition = transition, t = x) |
| 152 |
} |
|
| 153 | ||
| 154 |
# p11 ---- |
|
| 155 | ||
| 156 |
#' Helper Function for `log_p11()` |
|
| 157 |
#' |
|
| 158 |
#' @param x (`numeric`)\cr variable of integration. |
|
| 159 |
#' @param transition (`TransitionParameters`)\cr |
|
| 160 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 161 |
#' |
|
| 162 |
#' @return Hazard rate at the specified time for the transition from progression to death. |
|
| 163 |
#' @keywords internal |
|
| 164 |
p11Integ <- function(x, transition) {
|
|
| 165 | 8974x |
haz(transition = transition, t = x, trans = 3) |
| 166 |
} |
|
| 167 | ||
| 168 |
#' Probability of Remaining in Progression Between Two Time Points for Different Transition Models |
|
| 169 |
#' |
|
| 170 |
#' @param transition (`TransitionParameters`)\cr |
|
| 171 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 172 |
#' @param s (`numeric`)\cr lower time points. |
|
| 173 |
#' @param t (`numeric`)\cr higher time points. |
|
| 174 |
#' @return This returns the natural logarithm of the probability of remaining in progression (state 1) |
|
| 175 |
#' between two time points, conditional on being in state 1 at the lower time point. |
|
| 176 |
#' |
|
| 177 |
#' @export |
|
| 178 |
#' |
|
| 179 |
#' @examples |
|
| 180 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 181 |
#' log_p11(transition, 1, 3) |
|
| 182 |
log_p11 <- function(transition, s, t) {
|
|
| 183 | 431x |
assert_numeric(s, finite = TRUE, any.missing = FALSE, lower = 0) |
| 184 | 431x |
assert_numeric(t, finite = TRUE, any.missing = FALSE, lower = 0) |
| 185 | 431x |
assert_true(identical(length(s), length(t))) |
| 186 | 431x |
assert_true(all(t > s)) |
| 187 | ||
| 188 | 431x |
intval <- mapply(function(s, t) {
|
| 189 | 8973x |
stats::integrate(p11Integ, |
| 190 | 8973x |
lower = s, |
| 191 | 8973x |
upper = t, |
| 192 | 8973x |
transition |
| 193 | 8973x |
)$value |
| 194 | 431x |
}, s, t) |
| 195 | 431x |
-intval |
| 196 |
} |
|
| 197 | ||
| 198 |
# PFSOS ---- |
|
| 199 | ||
| 200 |
#' Helper Function for `survPFSOS()` |
|
| 201 |
#' |
|
| 202 |
#' @param u (`numeric`)\cr variable of integration. |
|
| 203 |
#' @param t (`numeric`)\cr time at which the value of the PFS*OS survival function is to be computed. |
|
| 204 |
#' @param transition (`TransitionParameters`)\cr |
|
| 205 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 206 |
#' |
|
| 207 |
#' @note Not all vectors `u` and `t` work here due to assertions in [log_p11()]. |
|
| 208 |
#' |
|
| 209 |
#' @return Numeric result of the integrand used to calculate the PFS*OS survival function. |
|
| 210 |
#' @keywords internal |
|
| 211 |
PFSOSInteg <- function(u, t, transition) {
|
|
| 212 | 429x |
exp(log_p11(transition, u, t / u) + log(survPFS(transition, u)) + log(haz(transition, u, 1))) |
| 213 |
} |
|
| 214 | ||
| 215 |
#' Survival Function of the Product PFS*OS for Different Transition Models |
|
| 216 |
#' |
|
| 217 |
#' @param t (`numeric`)\cr time at which the value of the PFS*OS survival function is to be computed. |
|
| 218 |
#' @param transition (`TransitionParameters`)\cr |
|
| 219 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 220 |
#' |
|
| 221 |
#' @return This returns the value of PFS*OS survival function at time t. |
|
| 222 |
#' @export |
|
| 223 |
#' |
|
| 224 |
#' @examples |
|
| 225 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 226 |
#' survPFSOS(0.4, transition) |
|
| 227 |
survPFSOS <- function(t, transition) {
|
|
| 228 | 19x |
sapply(t, function(x) {
|
| 229 | 249x |
intval <- stats::integrate(PFSOSInteg, lower = 0, upper = sqrt(x), x, transition)$value |
| 230 | 249x |
survPFS(transition, sqrt(x)) + intval |
| 231 |
}) |
|
| 232 |
} |
|
| 233 | ||
| 234 |
# correlation ---- |
|
| 235 | ||
| 236 |
#' Correlation of PFS and OS event times for Different Transition Models |
|
| 237 |
#' |
|
| 238 |
#' @param transition (`TransitionParameters`)\cr |
|
| 239 |
#' see [exponential_transition()], [weibull_transition()] or [piecewise_exponential()] for details. |
|
| 240 |
#' |
|
| 241 |
#' @return The correlation of PFS and OS. |
|
| 242 |
#' @export |
|
| 243 |
#' |
|
| 244 |
#' @examples |
|
| 245 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 246 |
#' corTrans(transition) |
|
| 247 |
corTrans <- function(transition) {
|
|
| 248 |
# E(PFS) & E(OS). |
|
| 249 | 2x |
expvalPFS <- stats::integrate(survPFS, |
| 250 | 2x |
lower = 0, upper = Inf, |
| 251 | 2x |
transition = transition |
| 252 | 2x |
)$value |
| 253 | ||
| 254 | 2x |
expvalOS <- stats::integrate(survOS, |
| 255 | 2x |
lower = 0, upper = Inf, |
| 256 | 2x |
transition = transition |
| 257 | 2x |
)$value |
| 258 | ||
| 259 |
# Var(PFS) & Var(OS). |
|
| 260 | 2x |
expvalPFS2 <- 2 * stats::integrate(expvalPFSInteg, |
| 261 | 2x |
lower = 0, upper = Inf, |
| 262 | 2x |
transition = transition |
| 263 | 2x |
)$value |
| 264 | ||
| 265 | 2x |
expvalOS2 <- 2 * stats::integrate(expvalOSInteg, |
| 266 | 2x |
lower = 0, upper = Inf, |
| 267 | 2x |
transition = transition |
| 268 | 2x |
)$value |
| 269 | ||
| 270 | 2x |
varPFS <- expvalPFS2 - expvalPFS^2 |
| 271 | ||
| 272 | 2x |
varOS <- expvalOS2 - expvalOS^2 |
| 273 | ||
| 274 |
# E(PFS*OS). |
|
| 275 | 2x |
expvalPFSOS <- stats::integrate(survPFSOS, |
| 276 | 2x |
lower = 0, upper = Inf, |
| 277 | 2x |
transition |
| 278 | 2x |
)$value |
| 279 | ||
| 280 |
# Cor(PFS, OS). |
|
| 281 | 2x |
(expvalPFSOS - expvalPFS * expvalOS) / sqrt(varPFS * varOS) |
| 282 |
} |
|
| 283 | ||
| 284 |
#' Correlation of PFS and OS event times for data from the IDM |
|
| 285 |
#' |
|
| 286 |
#' @param data (`data.frame`)\cr in the format produced by [getOneClinicalTrial()]. |
|
| 287 |
#' @param transition (`TransitionParameters` object)\cr specifying the assumed distribution of transition hazards. |
|
| 288 |
#' Initial parameters for optimization can be specified here. |
|
| 289 |
#' See [exponential_transition()] or [weibull_transition()] for details. |
|
| 290 |
#' @param bootstrap (`flag`)\cr if `TRUE` computes confidence interval via bootstrap. |
|
| 291 |
#' @param bootstrap_n (`count`)\cr number of bootstrap samples. |
|
| 292 |
#' @param conf_level (`proportion`)\cr confidence level for the confidence interval. |
|
| 293 |
#' |
|
| 294 |
#' @return The correlation of PFS and OS. |
|
| 295 |
#' @export |
|
| 296 |
#' |
|
| 297 |
#' @examples |
|
| 298 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 299 |
#' data <- getClinicalTrials( |
|
| 300 |
#' nRep = 1, nPat = c(100), seed = 1234, datType = "1rowTransition", |
|
| 301 |
#' transitionByArm = list(transition), dropout = list(rate = 0.5, time = 12), |
|
| 302 |
#' accrual = list(param = "intensity", value = 7) |
|
| 303 |
#' )[[1]] |
|
| 304 |
#' corPFSOS(data, transition = exponential_transition(), bootstrap = FALSE) |
|
| 305 |
#' \dontrun{
|
|
| 306 |
#' corPFSOS(data, transition = exponential_transition(), bootstrap = TRUE) |
|
| 307 |
#' } |
|
| 308 |
corPFSOS <- function(data, transition, bootstrap = TRUE, bootstrap_n = 100, conf_level = 0.95) {
|
|
| 309 | 1x |
assert_data_frame(data) |
| 310 | 1x |
assert_flag(bootstrap) |
| 311 | 1x |
assert_count(bootstrap_n) |
| 312 | 1x |
assert_number(conf_level, lower = 0.01, upper = 0.999) |
| 313 | ||
| 314 | 1x |
trans <- estimateParams(data, transition) |
| 315 | 1x |
res <- list("corPFSOS" = corTrans(trans))
|
| 316 | 1x |
if (bootstrap) {
|
| 317 | ! |
oplan <- future::plan(future::multisession, workers = parallelly::availableCores(omit = 1)) |
| 318 | ! |
on.exit(future::plan(oplan), add = TRUE) |
| 319 | ! |
ids <- lapply(1:bootstrap_n, function(x) sample(seq_len(nrow(data)), nrow(data), replace = TRUE)) |
| 320 | ! |
corBootstrap <- furrr::future_map_dbl(ids, ~ {
|
| 321 | ! |
furrr::furrr_options( |
| 322 | ! |
globals = list(data = data, transition = transition), |
| 323 | ! |
packages = c("simIDM")
|
| 324 |
) |
|
| 325 | ! |
b_sample <- data[.x, , drop = FALSE] |
| 326 | ! |
b_transition <- estimateParams(b_sample, transition) |
| 327 | ! |
corTrans(b_transition) |
| 328 |
}) |
|
| 329 | ! |
lowerQuantile <- (1 - conf_level) / 2 |
| 330 | ! |
upperQuantile <- lowerQuantile + conf_level |
| 331 | ! |
c(stats::quantile(corBootstrap, lowerQuantile), |
| 332 | ! |
"corPFSOS" = res, |
| 333 | ! |
stats::quantile(corBootstrap, upperQuantile) |
| 334 |
) |
|
| 335 | ! |
res$lower <- stats::quantile(corBootstrap, lowerQuantile) |
| 336 | ! |
res$upper <- stats::quantile(corBootstrap, upperQuantile) |
| 337 |
} |
|
| 338 | 1x |
res |
| 339 |
} |
| 1 |
#' Log-Rank Test for a Single Trial |
|
| 2 |
#' |
|
| 3 |
#' This function evaluates the significance of either PFS or OS endpoints in a trial, |
|
| 4 |
#' based on a pre-specified critical value. |
|
| 5 |
#' |
|
| 6 |
#' @param data (`data.frame`)\cr data frame containing entry and exit times of an |
|
| 7 |
#' illness-death model. See [getSimulatedData()] for details. |
|
| 8 |
#' @param typeEvent (`string`)\cr endpoint to be evaluated, possible values are `PFS` and `OS`. |
|
| 9 |
#' @param critical (positive `number`)\cr critical value of the log-rank test. |
|
| 10 |
#' |
|
| 11 |
#' @return Logical value indicating log-rank test significance. |
|
| 12 |
#' @export |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' transition1 <- exponential_transition(h01 = 0.06, h02 = 0.3, h12 = 0.3) |
|
| 16 |
#' transition2 <- exponential_transition(h01 = 0.1, h02 = 0.4, h12 = 0.3) |
|
| 17 |
#' simTrial <- getClinicalTrials( |
|
| 18 |
#' nRep = 1, nPat = c(800, 800), seed = 1234, datType = "1rowPatient", |
|
| 19 |
#' transitionByArm = list(transition1, transition2), dropout = list(rate = 0.5, time = 12), |
|
| 20 |
#' accrual = list(param = "intensity", value = 7) |
|
| 21 |
#' )[[1]] |
|
| 22 |
#' logRankTest(data = simTrial, typeEvent = "OS", critical = 3.4) |
|
| 23 |
logRankTest <- function(data, typeEvent = c("PFS", "OS"), critical) {
|
|
| 24 |
# Must be have the format of one row per patient (`datType` must be 1rowPatient` |
|
| 25 |
# in getClinicalTrials()), i.e. have 10 (if censored) or 11 columns. |
|
| 26 | 108x |
assert_data_frame(data, min.cols = 10, max.cols = 11) |
| 27 | 108x |
typeEvent <- match.arg(typeEvent) |
| 28 | 108x |
assert_positive_number(critical) |
| 29 | ||
| 30 | 108x |
if (typeEvent == "OS") {
|
| 31 | 54x |
time <- data$OStime |
| 32 | 54x |
event <- data$OSevent |
| 33 | 54x |
} else if (typeEvent == "PFS") {
|
| 34 | 54x |
time <- data$PFStime |
| 35 | 54x |
event <- data$PFSevent |
| 36 |
} |
|
| 37 | ||
| 38 | 108x |
logRank <- survival::survdiff(survival::Surv(time, event) ~ trt, data) |
| 39 | 108x |
sqrt(logRank$chisq) > critical |
| 40 |
} |
|
| 41 | ||
| 42 | ||
| 43 |
#' Helper function to conduct log-rank tests for either PFS or OS |
|
| 44 |
#' |
|
| 45 |
#' This function evaluates the significance of either PFS or OS endpoints for each trial |
|
| 46 |
#' in a list of trials, based on a pre-specified critical value. |
|
| 47 |
#' |
|
| 48 |
#' @param simTrials (`list`)\cr simulated trial data sets, see [getClinicalTrials()]. |
|
| 49 |
#' @param typeEvent (`string`)\cr endpoint to be evaluated, possible values are `PFS` and `OS`. |
|
| 50 |
#' @param eventNum (`integer`)\cr number of events required to trigger analysis. |
|
| 51 |
#' @param critical (positive `number`)\cr critical value of the log-rank test. |
|
| 52 |
#' |
|
| 53 |
#' @return Logical vector indicating log-rank test significance for each trial. |
|
| 54 |
#' @export |
|
| 55 |
#' |
|
| 56 |
#' @examples |
|
| 57 |
#' transition1 <- exponential_transition(h01 = 0.06, h02 = 0.3, h12 = 0.3) |
|
| 58 |
#' transition2 <- exponential_transition(h01 = 0.1, h02 = 0.4, h12 = 0.3) |
|
| 59 |
#' simTrials <- getClinicalTrials( |
|
| 60 |
#' nRep = 50, nPat = c(800, 800), seed = 1234, datType = "1rowPatient", |
|
| 61 |
#' transitionByArm = list(transition1, transition2), dropout = list(rate = 0.5, time = 12), |
|
| 62 |
#' accrual = list(param = "intensity", value = 7) |
|
| 63 |
#' ) |
|
| 64 |
#' passedLogRank(simTrials = simTrials, typeEvent = "PFS", eventNum = 300, critical = 2.4) |
|
| 65 |
#' @keywords internal |
|
| 66 |
passedLogRank <- function(simTrials, typeEvent, eventNum, critical) {
|
|
| 67 | 4x |
assert_list(simTrials, null.ok = FALSE) |
| 68 | 4x |
assert_positive_number(critical) |
| 69 | 4x |
assert_count(eventNum, positive = TRUE) |
| 70 | ||
| 71 |
# Censor simulated trials at time-point of OS/PFS analysis. |
|
| 72 | 4x |
trialsAna <- lapply( |
| 73 | 4x |
X = simTrials, |
| 74 | 4x |
FUN = censoringByNumberEvents, |
| 75 | 4x |
eventNum = eventNum, |
| 76 | 4x |
typeEvent = typeEvent |
| 77 |
) |
|
| 78 |
# Compute log-rank test for all trials for OS/PFS. |
|
| 79 | 4x |
unlist(lapply( |
| 80 | 4x |
X = trialsAna, |
| 81 | 4x |
FUN = logRankTest, |
| 82 | 4x |
typeEvent = typeEvent, |
| 83 | 4x |
critical = critical |
| 84 |
)) |
|
| 85 |
} |
|
| 86 | ||
| 87 |
#' Empirical Significance for a List of Simulated Trials |
|
| 88 |
#' |
|
| 89 |
#' This function computes four types of empirical significance — PFS, OS, at-least (significant |
|
| 90 |
#' in at least one of PFS/OS), and joint (significant in both PFS and OS) — using |
|
| 91 |
#' the log-rank test. Empirical significance is calculated as the proportion of significant |
|
| 92 |
#' results in simulated trials, each ending when a set number of PFS/OS events occur. |
|
| 93 |
#' Critical values for PFS and OS test significance must be specified. If trials simulate equal |
|
| 94 |
#' transition hazards across groups (H0), empirical significance estimates type I error; |
|
| 95 |
#' if they simulate differing transition hazards (H1), it estimates power. |
|
| 96 |
#' |
|
| 97 |
#' @param simTrials (`list`)\cr simulated trial data sets, see [getClinicalTrials()]. |
|
| 98 |
#' @param criticalPFS (positive `number`)\cr critical value of the log-rank test for PFS. |
|
| 99 |
#' @param criticalOS (positive `number`)\cr critical value of the log-rank test for OS. |
|
| 100 |
#' @param eventNumPFS (`integer`)\cr number of PFS events required to trigger PFS analysis. |
|
| 101 |
#' @param eventNumOS (`integer`)\cr number of OS events required to trigger OS analysis. |
|
| 102 |
#' |
|
| 103 |
#' @return This returns values of four measures of empirical significance. |
|
| 104 |
#' @export |
|
| 105 |
#' |
|
| 106 |
#' @examples |
|
| 107 |
#' transition1 <- exponential_transition(h01 = 0.06, h02 = 0.3, h12 = 0.3) |
|
| 108 |
#' transition2 <- exponential_transition(h01 = 0.1, h02 = 0.4, h12 = 0.3) |
|
| 109 |
#' simTrials <- getClinicalTrials( |
|
| 110 |
#' nRep = 50, nPat = c(800, 800), seed = 1234, datType = "1rowPatient", |
|
| 111 |
#' transitionByArm = list(transition1, transition2), dropout = list(rate = 0.5, time = 12), |
|
| 112 |
#' accrual = list(param = "intensity", value = 7) |
|
| 113 |
#' ) |
|
| 114 |
#' empSignificant( |
|
| 115 |
#' simTrials = simTrials, criticalPFS = 2.4, criticalOS = 2.2, |
|
| 116 |
#' eventNumPFS = 300, eventNumOS = 500 |
|
| 117 |
#' ) |
|
| 118 |
empSignificant <- function(simTrials, criticalPFS, criticalOS, eventNumPFS, eventNumOS) {
|
|
| 119 | 1x |
assert_list(simTrials, null.ok = FALSE) |
| 120 | 1x |
assert_positive_number(criticalPFS) |
| 121 | 1x |
assert_positive_number(criticalOS) |
| 122 | 1x |
assert_count(eventNumPFS, positive = TRUE) |
| 123 | 1x |
assert_count(eventNumOS, positive = TRUE) |
| 124 | ||
| 125 | 1x |
nRep <- length(simTrials) |
| 126 | 1x |
simTrials <- lapply( |
| 127 | 1x |
X = simTrials, |
| 128 | 1x |
FUN = function(x) if (ncol(x) == 9) getDatasetWideFormat(x) else x |
| 129 |
) |
|
| 130 | ||
| 131 |
# Which trials passed the log-rank test for PFS/OS? |
|
| 132 | 1x |
passedLogRankPFS <- passedLogRank( |
| 133 | 1x |
simTrials = simTrials, |
| 134 | 1x |
typeEvent = "PFS", |
| 135 | 1x |
eventNum = eventNumPFS, |
| 136 | 1x |
critical = criticalPFS |
| 137 |
) |
|
| 138 | 1x |
passedLogRankOS <- passedLogRank( |
| 139 | 1x |
simTrials = simTrials, |
| 140 | 1x |
typeEvent = "OS", |
| 141 | 1x |
eventNum = eventNumOS, |
| 142 | 1x |
critical = criticalOS |
| 143 |
) |
|
| 144 | ||
| 145 |
# Empirical significance is the fraction of trials with significant log-rank test: |
|
| 146 | 1x |
significantPFS <- sum(passedLogRankPFS) / nRep |
| 147 | 1x |
significantOS <- sum(passedLogRankOS) / nRep |
| 148 | ||
| 149 |
# Derived measures of significance: |
|
| 150 | 1x |
sumPassed <- passedLogRankPFS + passedLogRankOS |
| 151 |
# At least one of PFS/OS has significant log-rank test. |
|
| 152 | 1x |
significantAtLeastOne <- sum(sumPassed >= 1) / nRep |
| 153 |
# Both PFS/OS have significant log-rank tests. |
|
| 154 | 1x |
significantBoth <- sum(sumPassed == 2) / nRep |
| 155 | ||
| 156 | 1x |
list( |
| 157 | 1x |
"significantPFS" = significantPFS, |
| 158 | 1x |
"significantOS" = significantOS, |
| 159 | 1x |
"significantAtLeastOne" = significantAtLeastOne, |
| 160 | 1x |
"significantBoth" = significantBoth |
| 161 |
) |
|
| 162 |
} |
| 1 |
#' Transitions from the Intermediate State to the Absorbing State |
|
| 2 |
#' |
|
| 3 |
#' This function creates transition entry and exit times from the intermediate state to the absorbing state |
|
| 4 |
#' for an existing data frame containing the exit times out of the initial state. |
|
| 5 |
#' |
|
| 6 |
#' @param simDataOne (`data.frame`)\cr a data frame containing all patients with transitions |
|
| 7 |
#' into the intermediate state. See [getSimulatedData()] for details. |
|
| 8 |
#' @param transition (`TransitionParameters`)\cr transition parameters comprising |
|
| 9 |
#' `hazards`, corresponding `intervals` and `weibull_rates`, see [exponential_transition()], [piecewise_exponential()] |
|
| 10 |
#' and [weibull_transition()] for details. |
|
| 11 |
#' |
|
| 12 |
#' @return This returns a data frame with one row per patient for the second transition, |
|
| 13 |
#' i.e. the transition out of the intermediate |
|
| 14 |
#' state. This is a helper function of [getSimulatedData()]. |
|
| 15 |
#' |
|
| 16 |
#' @export |
|
| 17 |
#' |
|
| 18 |
#' @examples |
|
| 19 |
#' simDataOne <- data.frame( |
|
| 20 |
#' id = c(1:3), to = c(1, 1, 1), from = c(0, 0, 0), entry = c(0, 0, 0), |
|
| 21 |
#' exit = c(3, 5.6, 7.2), censTime = c(6.8, 5.9, 9.4) |
|
| 22 |
#' ) |
|
| 23 |
#' transition <- exponential_transition(1, 1.6, 0.3) |
|
| 24 |
#' getOneToTwoRows(simDataOne, transition) |
|
| 25 |
getOneToTwoRows <- function(simDataOne, transition) {
|
|
| 26 | 6207x |
assert_data_frame(simDataOne, ncols = 6) |
| 27 | 6207x |
assert_class(transition, "TransitionParameters") |
| 28 | ||
| 29 | 6207x |
id1 <- simDataOne$id |
| 30 | 6207x |
N1 <- nrow(simDataOne) |
| 31 | 6207x |
U1 <- stats::runif(N1) |
| 32 | 6207x |
to1 <- rep(2, N1) |
| 33 | 6207x |
from1 <- rep(1, N1) |
| 34 | 6207x |
entry1 <- simDataOne$exit |
| 35 | 6207x |
h12 <- transition$hazard$h12 |
| 36 | 6207x |
p12 <- transition$weibull_rates$p12 |
| 37 | 6207x |
pw12 <- transition$intervals$pw12 |
| 38 | ||
| 39 |
# Create waiting time in state 1. |
|
| 40 | 6207x |
wait_time1 <- if (transition$family == "exponential") {
|
| 41 | 2178x |
(-log(1 - U1)) / (h12) |
| 42 | 6207x |
} else if (transition$family == "Weibull") {
|
| 43 | 2026x |
getWaitTimeSum(U = U1, haz1 = h12, haz2 = 0, p1 = p12, p2 = 1, entry = entry1) |
| 44 | 6207x |
} else if (transition$family == "piecewise exponential") {
|
| 45 | 2003x |
getPCWDistr(U = U1, haz = h12, pw = pw12, t_0 = entry1) |
| 46 |
} |
|
| 47 | 6207x |
exit1 <- entry1 + wait_time1 |
| 48 | ||
| 49 |
# Add censoring. |
|
| 50 | 6207x |
censTime1 <- simDataOne$censTime |
| 51 | 6207x |
to1 <- ifelse(censTime1 < exit1, "cens", to1) |
| 52 | 6207x |
exit1 <- pmin(censTime1, exit1) |
| 53 | ||
| 54 | 6207x |
data.frame( |
| 55 | 6207x |
id = id1, |
| 56 | 6207x |
from = from1, |
| 57 | 6207x |
to = to1, |
| 58 | 6207x |
entry = entry1, |
| 59 | 6207x |
exit = exit1, |
| 60 | 6207x |
censTime = censTime1, |
| 61 | 6207x |
stringsAsFactors = FALSE |
| 62 |
) |
|
| 63 |
} |
|
| 64 | ||
| 65 |
#' Staggered Study Entry |
|
| 66 |
#' |
|
| 67 |
#' This function adds staggered study entry times to a simulated data set with illness-death model structure. |
|
| 68 |
#' |
|
| 69 |
#' @param simData (`data.frame`)\cr simulated data frame containing entry and exit times |
|
| 70 |
#' at individual study time scale. See [getSimulatedData()] for details. |
|
| 71 |
#' @param N (`int`)\cr number of patients. |
|
| 72 |
#' @param accrualParam (`string`)\cr possible values are 'time' or 'intensity'. |
|
| 73 |
#' @param accrualValue (`number`)\cr specifies the accrual intensity. For `accrualParam` equal time, |
|
| 74 |
#' it describes the length of the accrual period. For `accrualParam` equal intensity, it describes |
|
| 75 |
#' the number of patients recruited per time unit. If `accrualValue` is equal to 0, |
|
| 76 |
#' all patients start at calendar time 0 |
|
| 77 |
#' in the initial state. |
|
| 78 |
#' |
|
| 79 |
#' @return This returns a data set containing a single simulated study containing accrual times, |
|
| 80 |
#' i.e. staggered study entry. |
|
| 81 |
#' This is a helper function of [getSimulatedData()]. |
|
| 82 |
#' |
|
| 83 |
#' @export |
|
| 84 |
#' |
|
| 85 |
#' @examples |
|
| 86 |
#' simData <- data.frame( |
|
| 87 |
#' id = c(1, 1, 2, 3), from = c(0, 1, 0, 0), to = c(1, 2, "cens", 2), |
|
| 88 |
#' entry = c(0, 3, 0, 0), |
|
| 89 |
#' exit = c(3, 5.3, 5.6, 7.2), censTime = c(6.8, 6.8, 5.6, 9.4) |
|
| 90 |
#' ) |
|
| 91 |
#' addStaggeredEntry(simData, 3, accrualParam = "time", accrualValue = 5) |
|
| 92 |
addStaggeredEntry <- function(simData, N, accrualParam, accrualValue) {
|
|
| 93 | 6214x |
assert_choice(accrualParam, c("time", "intensity"))
|
| 94 | 6214x |
assert_number(accrualValue, lower = 0) |
| 95 | ||
| 96 |
# Get accrual times in calendar time per individual. |
|
| 97 |
# If no staggered study entry is present, all individuals have the same entry time 0. |
|
| 98 | 6214x |
entry_act <- if (accrualValue != 0) {
|
| 99 | 6200x |
if (accrualParam == "time") {
|
| 100 | 13x |
stats::runif(N, 0, accrualValue) |
| 101 | 6187x |
} else if (accrualParam == "intensity") {
|
| 102 | 6187x |
accrualTime <- N / accrualValue |
| 103 | 6187x |
stats::runif(N, 0, accrualTime) |
| 104 |
} |
|
| 105 | 6214x |
} else if (accrualValue == 0) {
|
| 106 | 14x |
rep(0, N) |
| 107 |
} |
|
| 108 | 6214x |
entryAct <- cbind(id = 1:N, entry_act) |
| 109 |
# Combine simulated data with actual entry time. |
|
| 110 |
# Generate actual times (actual entry time + individual time). |
|
| 111 | 6214x |
simData <- merge(simData, entryAct) |
| 112 | 6214x |
simData$entryAct <- simData$entry + simData$entry_act |
| 113 | 6214x |
simData$exitAct <- simData$exit + simData$entry_act |
| 114 | 6214x |
simData$censAct <- simData$censTime + simData$entry_act |
| 115 |
# Delete temporary helper columns. |
|
| 116 | 6214x |
simData$entry_act <- NULL |
| 117 | 6214x |
simData[, c("censTime")] <- list(NULL)
|
| 118 | 6214x |
simData |
| 119 |
} |
|
| 120 | ||
| 121 |
#' Simulate Data Set from an Illness-Death Model |
|
| 122 |
#' |
|
| 123 |
#' This function creates a single simulated data set for a single treatment arm. It simulates data |
|
| 124 |
#' from an illness-death model with one row per transition and subject. |
|
| 125 |
#' |
|
| 126 |
#' @param N (`int`)\cr number of patients. |
|
| 127 |
#' @param transition (`TransitionParameters`)\cr transition parameters comprising |
|
| 128 |
#' `hazards`, corresponding `intervals` and `weibull_rates`, see [exponential_transition()], [piecewise_exponential()] |
|
| 129 |
#' and [weibull_transition()] for details. |
|
| 130 |
#' @param dropout (`list`)\cr specifies drop-out probability. |
|
| 131 |
#' Random censoring times are generated using exponential distribution. `dropout$rate` specifies |
|
| 132 |
#' the drop-out probability per `dropout$time` time units. |
|
| 133 |
#' If `dropout$rate` is equal to 0, then no censoring is applied. |
|
| 134 |
#' @param accrual (`list`)\cr specifies accrual intensity. See [addStaggeredEntry()] for details. |
|
| 135 |
#' |
|
| 136 |
#' @return This returns a data frame with one row per transition per individual. |
|
| 137 |
#' @details The output data set contains the following columns: |
|
| 138 |
#' - id (`integer`): patient id. |
|
| 139 |
#' - from (`numeric`): starting state of the transition. |
|
| 140 |
#' - to (`character`): final state of the transition. |
|
| 141 |
#' - entry (`numeric`): entry time of the transition on the individual time scale. |
|
| 142 |
#' - exit (`numeric`): exit time of the transition on the individual time scale. |
|
| 143 |
#' - entryAct (`numeric`): entry time of the transition on study time scale. |
|
| 144 |
#' - exitAct (`numeric`): exit time of the transition on study time scale. |
|
| 145 |
#' - censAct (`numeric`): censoring time of the individual on study time scale. |
|
| 146 |
#' @export |
|
| 147 |
#' |
|
| 148 |
#' @examples |
|
| 149 |
#' getSimulatedData( |
|
| 150 |
#' N = 10, |
|
| 151 |
#' transition = exponential_transition(h01 = 1, h02 = 1.5, h12 = 1), |
|
| 152 |
#' dropout = list(rate = 0.3, time = 1), |
|
| 153 |
#' accrual = list(param = "time", value = 5) |
|
| 154 |
#' ) |
|
| 155 |
getSimulatedData <- function(N, |
|
| 156 |
transition = exponential_transition(h01 = 1, h02 = 1, h12 = 1), |
|
| 157 |
dropout = list(rate = 0, time = 12), |
|
| 158 |
accrual = list(param = "time", value = 0)) {
|
|
| 159 |
# Check input parameters. |
|
| 160 | 6210x |
assert_int(N, lower = 1L) |
| 161 | 6210x |
assert_class(transition, "TransitionParameters") |
| 162 | 6210x |
assert_list(dropout) |
| 163 | ||
| 164 | 6210x |
assert(check_number(dropout$rate, lower = 0, upper = 1), |
| 165 | 6210x |
check_number(dropout$time), |
| 166 | 6210x |
check_true(dropout$time > 0, ), |
| 167 | 6210x |
combine = "and", .var.name = "dropout" |
| 168 |
) |
|
| 169 | 6210x |
assert_list(accrual) |
| 170 |
# Initialize transition hazards, Weibull rates and intervals. |
|
| 171 | 6210x |
h <- transition$hazard |
| 172 | 6210x |
p <- transition$weibull_rates |
| 173 | 6210x |
pw <- transition$intervals |
| 174 | ||
| 175 |
# Get rate parameter for exponential distributed censoring times. |
|
| 176 |
# if rate=0, no censoring is applied. |
|
| 177 | 6210x |
censRate <- if (dropout$rate > 0) {
|
| 178 | 198x |
-log(1 - dropout$rate) / dropout$time |
| 179 |
} else {
|
|
| 180 | 6012x |
0 |
| 181 |
} |
|
| 182 |
# Censoring times for all individuals, infinity if no censoring is applied. |
|
| 183 | 6210x |
censTime <- if (dropout$rate > 0) {
|
| 184 | 198x |
stats::rexp(N, censRate) |
| 185 |
} else {
|
|
| 186 | 6012x |
rep(Inf, N) |
| 187 |
} |
|
| 188 | ||
| 189 |
# All individuals start in the initial state. |
|
| 190 | 6210x |
entry <- rep(0, N) |
| 191 | 6210x |
from <- rep(0, N) |
| 192 |
# Waiting time in the initial state 0. |
|
| 193 | 6210x |
U <- stats::runif(N) |
| 194 |
# Exponential or Weibull distributed survival times. |
|
| 195 | 6210x |
if (transition$family %in% c("exponential", "Weibull")) {
|
| 196 |
# For distribution of waiting time in the initial state the all-cause hazard (h01+h02) is needed. |
|
| 197 | 4203x |
wait_time <- getWaitTimeSum(U, h$h01, h$h02, p$p01, p$p02, entry) |
| 198 |
# A binomial experiment decides on death or progression. |
|
| 199 | 4203x |
numerator <- p$p01 * h$h01 * wait_time^(p$p01 - 1) # nolint |
| 200 | 4203x |
denumerator <- numerator + p$p02 * h$h02 * wait_time^(p$p02 - 1) # nolint |
| 201 | ||
| 202 |
# Piecewise exponential distributed survival times. |
|
| 203 | 2007x |
} else if (transition$family == "piecewise exponential") {
|
| 204 | 2007x |
Sum_PCW <- getSumPCW(h$h01, h$h02, pw$pw01, pw$pw02) |
| 205 | 2007x |
wait_time <- getPCWDistr(U, Sum_PCW$hazards, Sum_PCW$intervals, entry) |
| 206 |
# A binomial experiment decides on death or progression. |
|
| 207 | 2007x |
numerator <- getPWCHazard(h$h01, pw$pw01, wait_time) |
| 208 | 2007x |
denumerator <- getPWCHazard(h$h01, pw$pw01, wait_time) + |
| 209 | 2007x |
getPWCHazard(h$h02, pw$pw02, wait_time) |
| 210 |
} |
|
| 211 | ||
| 212 | 6210x |
to_prob <- stats::rbinom(N, 1, numerator / denumerator) |
| 213 | 6210x |
to <- ifelse(to_prob == 0, 2, 1) |
| 214 | 6210x |
exit <- wait_time |
| 215 | ||
| 216 |
# Add censoring. |
|
| 217 | 6210x |
to <- ifelse(censTime < wait_time, "cens", to) |
| 218 | 6210x |
exit <- pmin(censTime, wait_time) |
| 219 | ||
| 220 | 6210x |
simData <- data.frame( |
| 221 | 6210x |
id = 1:N, from = from, to = to, entry = entry, exit = exit, |
| 222 | 6210x |
censTime = censTime, stringsAsFactors = FALSE |
| 223 |
) |
|
| 224 | 6210x |
is_intermediate_state <- simData$to == 1 |
| 225 | 6210x |
if (any(is_intermediate_state)) {
|
| 226 |
# Add 1 -> 2 transition only for patients that are in the intermediate state 1. |
|
| 227 | 6206x |
simDataOne <- simData[is_intermediate_state, ] |
| 228 | 6206x |
newrows <- getOneToTwoRows(simDataOne, transition) |
| 229 | 6206x |
simData <- rbind(simData, newrows) |
| 230 | 6206x |
simData <- simData[order(simData$id), ] |
| 231 |
} |
|
| 232 |
# Add staggered study entry, i.e. study entry at calendar time. |
|
| 233 | 6210x |
simData <- addStaggeredEntry( |
| 234 | 6210x |
simData = simData, |
| 235 | 6210x |
N = N, |
| 236 | 6210x |
accrualParam = accrual$param, |
| 237 | 6210x |
accrualValue = accrual$value |
| 238 |
) |
|
| 239 | 6210x |
simData$to <- as.character(simData$to) |
| 240 | 6210x |
simData |
| 241 |
} |
| 1 |
#' Preparation of a Data Set to Compute Log-likelihood |
|
| 2 |
#' |
|
| 3 |
#' @param data (`data.frame`)\cr containing entry and exit times of an illness-death model. |
|
| 4 |
#' See [getOneClinicalTrial()] for details. |
|
| 5 |
#' |
|
| 6 |
#' @return This function returns a data set with one row per patient and transition, when the patient is at risk. |
|
| 7 |
#' @export |
|
| 8 |
#' |
|
| 9 |
#' @details |
|
| 10 |
#' The output data set contains the following columns: |
|
| 11 |
#' - id (`integer`): patient id. |
|
| 12 |
#' - from (`integer`): start event state. |
|
| 13 |
#' - to (`integer`): end event state. |
|
| 14 |
#' - trans (`integer`): transition (1, 2 or 3) identifier |
|
| 15 |
#' - `1`: Transition from state 0 (stable) to 1 (progression). |
|
| 16 |
#' - `2`: Transition from state 0 (stable) to 2 (death). |
|
| 17 |
#' - `3`: Transition from state 1 (progression) to 2 (death). |
|
| 18 |
#' - entry (`numeric`): time at which the patient begins to be at risk for the transition. |
|
| 19 |
#' - exit (`numeric`): time at which the patient ends to be at risk for the transition. |
|
| 20 |
#' - status (`logical`): event indicator for the transition. |
|
| 21 |
#' |
|
| 22 |
#' @examples |
|
| 23 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 24 |
#' simData <- getOneClinicalTrial( |
|
| 25 |
#' nPat = c(30), transitionByArm = list(transition), |
|
| 26 |
#' dropout = list(rate = 0.8, time = 12), |
|
| 27 |
#' accrual = list(param = "time", value = 1) |
|
| 28 |
#' ) |
|
| 29 |
#' prepareData(simData) |
|
| 30 |
prepareData <- function(data) {
|
|
| 31 | 8x |
assert_data_frame(data, min.cols = 9, max.cols = 11) |
| 32 | 8x |
colNames <- c( |
| 33 | 8x |
"id", "trt", "PFStime", "CensoredPFS", "PFSevent", "OStime", |
| 34 | 8x |
"CensoredOS", "OSevent", "recruitTime", |
| 35 | 8x |
"OStimeCal", "PFStimeCal" |
| 36 |
) |
|
| 37 | 8x |
if (!all(names(data) %in% colNames)) {
|
| 38 | 1x |
data <- getDatasetWideFormat(data) |
| 39 |
} |
|
| 40 | ||
| 41 |
# Transform simIDM trial data to log-likelihood-compatible format. |
|
| 42 |
# Suppress warning about how msprep handles 1 -> 3 transitions. |
|
| 43 | 8x |
dataNew <- suppressWarnings(mstate::msprep( |
| 44 | 8x |
time = c("recruitTime", "PFStime", "OStime"),
|
| 45 | 8x |
status = c("trt", "PFSevent", "OSevent"),
|
| 46 | 8x |
data = data, |
| 47 | 8x |
trans = mstate::trans.illdeath(), |
| 48 | 8x |
id = data$id |
| 49 |
)) |
|
| 50 | 8x |
cols <- which(names(dataNew) %in% c("Tstart", "Tstop"))
|
| 51 | 8x |
names(dataNew)[cols] <- c("entry", "exit")
|
| 52 |
# Correct msprep results for uncensored PFS=OS events. |
|
| 53 | 8x |
ids <- data$id[data$PFStimeCal == data$OStimeCal & data$CensoredPFS == 0] |
| 54 | 8x |
dataNew <- dataNew[!(dataNew$id %in% ids & dataNew$trans == 3), ] |
| 55 | 8x |
dataNew$status[dataNew$id %in% ids] <- abs(dataNew$status[dataNew$id %in% ids] - 1) |
| 56 | ||
| 57 | 8x |
as.data.frame(dataNew[, -which(names(dataNew) == "time")], row.names = seq_len(nrow(dataNew))) |
| 58 |
} |
|
| 59 | ||
| 60 |
#' Compute the Negative Log-Likelihood for a Given Data Set and Transition Model |
|
| 61 |
#' |
|
| 62 |
#' @param transition (`ExponentialTransition` or `WeibullTransition`)\cr |
|
| 63 |
#' see [exponential_transition()] or [weibull_transition()] for details. |
|
| 64 |
#' @param data (`data.frame`)\cr in the format created by [prepareData()]. |
|
| 65 |
#' |
|
| 66 |
#' @return The value of the negative log-likelihood. |
|
| 67 |
#' @export |
|
| 68 |
#' |
|
| 69 |
#' @details |
|
| 70 |
#' Calculates the negative log-likelihood for a given data set and transition model. It uses the hazard |
|
| 71 |
#' and survival functions specific to the transition model. |
|
| 72 |
#' |
|
| 73 |
#' @examples |
|
| 74 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 75 |
#' simData <- getOneClinicalTrial( |
|
| 76 |
#' nPat = c(30), transitionByArm = list(transition), |
|
| 77 |
#' dropout = list(rate = 0.8, time = 12), |
|
| 78 |
#' accrual = list(param = "time", value = 1) |
|
| 79 |
#' ) |
|
| 80 |
#' negLogLik(transition, prepareData(simData)) |
|
| 81 |
negLogLik <- function(transition, data) {
|
|
| 82 | 290x |
with( |
| 83 | 290x |
data, |
| 84 | 290x |
-sum(log( |
| 85 | 290x |
haz(transition, exit, trans)^status * survTrans(transition, exit, trans) / |
| 86 | 290x |
survTrans(transition, entry, trans) |
| 87 |
)) |
|
| 88 |
) |
|
| 89 |
} |
|
| 90 | ||
| 91 |
#' Hazard Function for Different Transition Models |
|
| 92 |
#' |
|
| 93 |
#' @param transition (`ExponentialTransition` or `WeibullTransition`)\cr |
|
| 94 |
#' see [exponential_transition()] or [weibull_transition()] for details. |
|
| 95 |
#' @param t (`numeric`)\cr time at which hazard is to be computed. |
|
| 96 |
#' @param trans (`integer`)\cr index specifying the transition type. |
|
| 97 |
#' |
|
| 98 |
#' @details |
|
| 99 |
#' The transition types are: |
|
| 100 |
#' - `1`: Transition from state 0 (stable) to 1 (progression). |
|
| 101 |
#' - `2`: Transition from state 0 (stable) to 2 (death). |
|
| 102 |
#' - `3`: Transition from state 1 (progression) to 2 (death). |
|
| 103 |
#' |
|
| 104 |
#' @return The hazard rate for the specified transition and time. |
|
| 105 |
#' @export |
|
| 106 |
#' |
|
| 107 |
#' @examples |
|
| 108 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 109 |
#' haz(transition, 0.4, 2) |
|
| 110 |
haz <- function(transition, t, trans) {
|
|
| 111 | 9698x |
assert_class(transition, "TransitionParameters") |
| 112 | 9698x |
assert_numeric(t, lower = 0) |
| 113 | 9698x |
assert_subset(trans, c(1, 2, 3)) |
| 114 | 9698x |
UseMethod("haz")
|
| 115 |
} |
|
| 116 | ||
| 117 |
#' @describeIn haz for an exponential transition model. |
|
| 118 |
#' @export |
|
| 119 |
#' |
|
| 120 |
#' @examples |
|
| 121 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 122 |
#' haz(transition, 0.4, 2) |
|
| 123 |
haz.ExponentialTransition <- function(transition, t, trans) {
|
|
| 124 |
# params (in this order): h01, h02, h12. |
|
| 125 | 7233x |
params <- unlist(transition$hazards, use.names = FALSE) |
| 126 | 7233x |
rep(params[trans], length(t) / length(trans)) |
| 127 |
} |
|
| 128 | ||
| 129 |
#' @describeIn haz for the Weibull transition model. |
|
| 130 |
#' @export |
|
| 131 |
#' |
|
| 132 |
#' @examples |
|
| 133 |
#' transition <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 2, p02 = 2.5, p12 = 3) |
|
| 134 |
#' haz(transition, 0.4, 2) |
|
| 135 |
haz.WeibullTransition <- function(transition, t, trans) {
|
|
| 136 |
# params (in this order): h01, h02, h12, p01, p02, p12. |
|
| 137 | 2311x |
params <- c(unlist(transition$hazards, use.names = FALSE), unlist(transition$weibull_rates, use.names = FALSE)) |
| 138 | 2311x |
params[trans] * params[trans + 3] * t^(params[trans + 3] - 1) |
| 139 |
} |
|
| 140 | ||
| 141 |
#' @describeIn haz for the piecewise constant transition model. |
|
| 142 |
#' @export |
|
| 143 |
#' |
|
| 144 |
#' @examples |
|
| 145 |
#' transition <- piecewise_exponential( |
|
| 146 |
#' h01 = c(1, 1, 1), h02 = c(1.5, 0.5, 1), h12 = c(1, 1, 1), |
|
| 147 |
#' pw01 = c(0, 3, 8), pw02 = c(0, 6, 7), pw12 = c(0, 8, 9) |
|
| 148 |
#' ) |
|
| 149 |
#' haz(transition, 6, 2) |
|
| 150 |
haz.PWCTransition <- function(transition, t, trans) {
|
|
| 151 | 154x |
getPWCHazard(unlist(transition$hazards[trans], use.names = FALSE), |
| 152 | 154x |
unlist(transition$intervals[trans], use.names = FALSE), |
| 153 | 154x |
x = t |
| 154 |
) |
|
| 155 |
} |
|
| 156 | ||
| 157 |
#' Survival Function for Different Transition Models |
|
| 158 |
#' |
|
| 159 |
#' @param transition (`ExponentialTransition` or `WeibullTransition`)\cr |
|
| 160 |
#' see [exponential_transition()] or [weibull_transition()] for details. |
|
| 161 |
#' @param t (`numeric`)\cr time at which survival probability is to be computed. |
|
| 162 |
#' @param trans (`integer`)\cr index specifying the transition type. |
|
| 163 |
#' |
|
| 164 |
#' @return The survival probability for the specified transition and time. |
|
| 165 |
#' @export |
|
| 166 |
#' |
|
| 167 |
#' @examples |
|
| 168 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 169 |
#' survTrans(transition, 0.4, 2) |
|
| 170 |
survTrans <- function(transition, t, trans) {
|
|
| 171 | 582x |
assert_class(transition, "TransitionParameters") |
| 172 | 582x |
assert_numeric(t, lower = 0) |
| 173 | 582x |
assert_subset(trans, c(1, 2, 3)) |
| 174 | 582x |
UseMethod("survTrans")
|
| 175 |
} |
|
| 176 | ||
| 177 |
#' @describeIn survTrans for the Exponential Transition Model |
|
| 178 |
#' @export |
|
| 179 |
#' |
|
| 180 |
#' @examples |
|
| 181 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 182 |
#' survTrans(transition, 0.4, 2) |
|
| 183 |
survTrans.ExponentialTransition <- function(transition, t, trans) {
|
|
| 184 |
# params (in this order): h01, h02, h12. |
|
| 185 | 187x |
params <- unlist(transition$hazards, use.names = FALSE) |
| 186 | 187x |
exp(-params[trans] * t) |
| 187 |
} |
|
| 188 | ||
| 189 |
#' @describeIn survTrans for the Weibull Transition Model |
|
| 190 |
#' @export |
|
| 191 |
#' |
|
| 192 |
#' @examples |
|
| 193 |
#' transition <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 2, p02 = 2.5, p12 = 3) |
|
| 194 |
#' survTrans(transition, 0.4, 2) |
|
| 195 |
survTrans.WeibullTransition <- function(transition, t, trans) {
|
|
| 196 |
# params (in this order): h01, h02, h12, p01, p02, p12. |
|
| 197 | 395x |
params <- c(unlist(transition$hazards, use.names = FALSE), unlist(transition$weibull_rates, use.names = FALSE)) |
| 198 | 395x |
exp(-params[trans] * t^params[trans + 3]) |
| 199 |
} |
|
| 200 | ||
| 201 |
#' Retrieve Initial Parameter Vectors for Likelihood Maximization |
|
| 202 |
#' |
|
| 203 |
#' @param transition (`ExponentialTransition` or `WeibullTransition`)\cr containing the initial parameters. |
|
| 204 |
#' See [exponential_transition()] or [weibull_transition()] for details. |
|
| 205 |
#' |
|
| 206 |
#' @return The numeric vector of initial parameters for likelihood maximization. |
|
| 207 |
#' @export |
|
| 208 |
#' |
|
| 209 |
#' @examples |
|
| 210 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 211 |
#' getInit(transition) |
|
| 212 |
getInit <- function(transition) {
|
|
| 213 | 5x |
assert_class(transition, "TransitionParameters") |
| 214 | 5x |
UseMethod("getInit")
|
| 215 |
} |
|
| 216 | ||
| 217 |
#' @describeIn getInit for the Exponential Transition Model |
|
| 218 |
#' @export |
|
| 219 |
#' |
|
| 220 |
#' @examples |
|
| 221 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 222 |
#' getInit(transition) |
|
| 223 |
getInit.ExponentialTransition <- function(transition) {
|
|
| 224 | 3x |
unlist(transition$hazards, use.names = FALSE) |
| 225 |
} |
|
| 226 | ||
| 227 |
#' @describeIn getInit for the Weibull Transition Model |
|
| 228 |
#' @export |
|
| 229 |
#' |
|
| 230 |
#' @examples |
|
| 231 |
#' transition <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 2, p02 = 2.5, p12 = 3) |
|
| 232 |
#' getInit(transition) |
|
| 233 |
getInit.WeibullTransition <- function(transition) {
|
|
| 234 | 2x |
c(unlist(transition$hazards, use.names = FALSE), unlist(transition$weibull_rates, use.names = FALSE)) |
| 235 |
} |
|
| 236 | ||
| 237 |
#' Generate the Target Function for Optimization |
|
| 238 |
#' |
|
| 239 |
#' @param transition (`TransitionParameters`)\cr |
|
| 240 |
#' specifying the distribution family. See [exponential_transition()] or [weibull_transition()] for details. |
|
| 241 |
#' |
|
| 242 |
#' @return Function that calculates the negative log-likelihood for the given parameters. |
|
| 243 |
#' @export |
|
| 244 |
#' |
|
| 245 |
#' @details |
|
| 246 |
#' This function creates a target function for optimization, computing the negative log-likelihood for given |
|
| 247 |
#' parameters, data, and transition model type. |
|
| 248 |
#' |
|
| 249 |
#' @examples |
|
| 250 |
#' transition <- exponential_transition(2, 1.3, 0.8) |
|
| 251 |
#' simData <- getOneClinicalTrial( |
|
| 252 |
#' nPat = c(30), transitionByArm = list(transition), |
|
| 253 |
#' dropout = list(rate = 0.8, time = 12), |
|
| 254 |
#' accrual = list(param = "time", value = 1) |
|
| 255 |
#' ) |
|
| 256 |
#' params <- c(1.2, 1.5, 1.6) # For ExponentialTransition |
|
| 257 |
#' data <- prepareData(simData) |
|
| 258 |
#' transition <- exponential_transition() |
|
| 259 |
#' fun <- getTarget(transition) |
|
| 260 |
#' fun(params, data) |
|
| 261 |
getTarget <- function(transition) {
|
|
| 262 | 5x |
assert_class(transition, "TransitionParameters") |
| 263 | 5x |
UseMethod("getTarget", transition)
|
| 264 |
} |
|
| 265 | ||
| 266 |
#' @describeIn getTarget for the Exponential Transition Model |
|
| 267 |
#' @export |
|
| 268 |
#' |
|
| 269 |
#' @examples |
|
| 270 |
#' transition <- exponential_transition(2, 1.3, 0.8) |
|
| 271 |
#' simData <- getOneClinicalTrial( |
|
| 272 |
#' nPat = c(30), transitionByArm = list(transition), |
|
| 273 |
#' dropout = list(rate = 0.8, time = 12), |
|
| 274 |
#' accrual = list(param = "time", value = 1) |
|
| 275 |
#' ) |
|
| 276 |
#' params <- c(1.2, 1.5, 1.6) |
|
| 277 |
#' data <- prepareData(simData) |
|
| 278 |
#' transition <- exponential_transition() |
|
| 279 |
#' target <- getTarget(transition) |
|
| 280 |
#' target(params, data) |
|
| 281 |
getTarget.ExponentialTransition <- function(transition) {
|
|
| 282 | 3x |
function(params, data) {
|
| 283 | 92x |
negLogLik(transition = exponential_transition(h01 = params[1], h02 = params[2], h12 = params[3]), data = data) |
| 284 |
} |
|
| 285 |
} |
|
| 286 | ||
| 287 |
#' @describeIn getTarget for the Weibull Transition Model |
|
| 288 |
#' @export |
|
| 289 |
#' |
|
| 290 |
#' @examples |
|
| 291 |
#' transition <- weibull_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6, p01 = 2, p02 = 2.5, p12 = 3) |
|
| 292 |
#' simData <- getOneClinicalTrial( |
|
| 293 |
#' nPat = c(30), transitionByArm = list(transition), |
|
| 294 |
#' dropout = list(rate = 0.8, time = 12), |
|
| 295 |
#' accrual = list(param = "time", value = 1) |
|
| 296 |
#' ) |
|
| 297 |
#' params <- c(1.2, 1.5, 1.6, 0.8, 1.3, 1.1) |
|
| 298 |
#' data <- prepareData(simData) |
|
| 299 |
#' transition <- weibull_transition() |
|
| 300 |
#' target <- getTarget(transition) |
|
| 301 |
#' target(params, data) |
|
| 302 |
getTarget.WeibullTransition <- function(transition) {
|
|
| 303 | 2x |
function(params, data) {
|
| 304 | 196x |
negLogLik(transition = weibull_transition( |
| 305 | 196x |
h01 = params[1], h02 = params[2], h12 = params[3], |
| 306 | 196x |
p01 = params[4], p02 = params[5], p12 = params[6] |
| 307 | 196x |
), data = data) |
| 308 |
} |
|
| 309 |
} |
|
| 310 | ||
| 311 |
#' Format Results of Parameter Estimation for Different Transition Models |
|
| 312 |
#' |
|
| 313 |
#' @param transition (`TransitionParameters`)\cr |
|
| 314 |
#' see [exponential_transition()] or [weibull_transition()] for details. |
|
| 315 |
#' @param res (`numeric` vector)\cr vector of parameter estimates from the likelihood maximization procedure. |
|
| 316 |
#' |
|
| 317 |
#' @return Returns a `TransitionParameters` object with parameter estimates. |
|
| 318 |
#' @export |
|
| 319 |
#' |
|
| 320 |
#' @examples |
|
| 321 |
#' results <- c(1.2, 1.5, 1.6) |
|
| 322 |
#' getResults(exponential_transition(), results) |
|
| 323 |
getResults <- function(transition, res) {
|
|
| 324 | 5x |
UseMethod("getResults")
|
| 325 |
} |
|
| 326 | ||
| 327 |
#' @describeIn getResults for the Exponential Transition Model |
|
| 328 |
#' @export |
|
| 329 |
#' |
|
| 330 |
#' @examples |
|
| 331 |
#' results <- c(1.2, 1.5, 1.6) |
|
| 332 |
#' getResults(exponential_transition(), results) |
|
| 333 |
getResults.ExponentialTransition <- function(transition, res) {
|
|
| 334 | 3x |
exponential_transition(h01 = res[1], h02 = res[2], h12 = res[3]) |
| 335 |
} |
|
| 336 | ||
| 337 |
#' @describeIn getResults for the Weibull Transition Model |
|
| 338 |
#' @export |
|
| 339 |
#' |
|
| 340 |
#' @examples |
|
| 341 |
#' results <- c(1.2, 1.5, 1.6, 2, 2.5, 1) |
|
| 342 |
#' getResults(weibull_transition(), results) |
|
| 343 |
getResults.WeibullTransition <- function(transition, res) {
|
|
| 344 | 2x |
weibull_transition( |
| 345 | 2x |
h01 = res[1], h02 = res[2], h12 = res[3], |
| 346 | 2x |
p01 = res[4], p02 = res[5], p12 = res[6] |
| 347 |
) |
|
| 348 |
} |
|
| 349 | ||
| 350 |
#' Estimate Parameters of the Multistate Model Using Clinical Trial Data |
|
| 351 |
#' |
|
| 352 |
#' @param data (`data.frame`)\cr in the format produced by [getOneClinicalTrial()]. |
|
| 353 |
#' @param transition (`TransitionParameters` object)\cr specifying the assumed distribution of transition hazards. |
|
| 354 |
#' Initial parameters for optimization can be specified here. |
|
| 355 |
#' See [exponential_transition()] or [weibull_transition()] for details. |
|
| 356 |
#' |
|
| 357 |
#' @return Returns a `TransitionParameters` object with the estimated parameters. |
|
| 358 |
#' @export |
|
| 359 |
#' |
|
| 360 |
#' @details |
|
| 361 |
#' This function estimates parameters for transition models using clinical trial data. |
|
| 362 |
#' The `transition` object can be initialized with starting values for parameter estimation. |
|
| 363 |
#' It uses [stats::optim()] to optimize the parameters. |
|
| 364 |
#' |
|
| 365 |
#' @examples |
|
| 366 |
#' transition <- exponential_transition(h01 = 2, h02 = 1.4, h12 = 1.6) |
|
| 367 |
#' simData <- getOneClinicalTrial( |
|
| 368 |
#' nPat = c(30), transitionByArm = list(transition), |
|
| 369 |
#' dropout = list(rate = 0.3, time = 12), |
|
| 370 |
#' accrual = list(param = "time", value = 1) |
|
| 371 |
#' ) |
|
| 372 |
#' # Initialize transition with desired starting values for optimization: |
|
| 373 |
#' transition <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 374 |
#' estimate <- estimateParams(simData, transition) |
|
| 375 |
estimateParams <- function(data, transition) {
|
|
| 376 | 3x |
data <- prepareData(data) |
| 377 | 3x |
par <- getInit(transition) |
| 378 | 3x |
target <- getTarget(transition) |
| 379 | ||
| 380 | 3x |
res <- stats::optim( |
| 381 | 3x |
par = par, |
| 382 | 3x |
fn = target, |
| 383 | 3x |
method = "L-BFGS-B", |
| 384 | 3x |
lower = 1e-3, |
| 385 | 3x |
data = data |
| 386 | 3x |
)$par |
| 387 | ||
| 388 | 3x |
getResults(transition, res) |
| 389 |
} |
| 1 |
#' Simulation of a Single Oncology Clinical Trial |
|
| 2 |
#' |
|
| 3 |
#' This function creates a data set with a single simulated oncology clinical trial with one row per transition |
|
| 4 |
#' based on an illness-death model. Studies with an arbitrary number of treatment arms are possible. |
|
| 5 |
#' |
|
| 6 |
#' @param nPat (`integer`)\cr numbers of patients per treatment arm. |
|
| 7 |
#' @param transitionByArm (`list`) \cr transition parameters for each treatment group. |
|
| 8 |
#' See [exponential_transition()], [piecewise_exponential()] and [weibull_transition()] for details. |
|
| 9 |
#' @param dropout dropout (`list`)\cr specifies drop-out probability. See [getSimulatedData()] for details. |
|
| 10 |
#' Can be specified either as one list that should be applied to all treatment groups or a separate list |
|
| 11 |
#' for each treatment group. |
|
| 12 |
#' @param accrual accrual (`list`)\cr specifies accrual intensity. See [addStaggeredEntry()] for details. |
|
| 13 |
#' Can be specified either as one list that should be applied to all treatment groups or a separate list |
|
| 14 |
#' for each treatment group. |
|
| 15 |
#' |
|
| 16 |
#' @return This returns a data frame with one simulated clinical trial and multiple treatment arms. |
|
| 17 |
#' See [getSimulatedData()] for the explanation of the columns. The column `trt` contains the treatment indicator. |
|
| 18 |
#' This is a helper function of [getClinicalTrials()]. |
|
| 19 |
#' @export |
|
| 20 |
#' |
|
| 21 |
#' @examples |
|
| 22 |
#' transition1 <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 23 |
#' transition2 <- exponential_transition(h01 = 1, h02 = 1.3, h12 = 1.7) |
|
| 24 |
#' transition3 <- exponential_transition(h01 = 1.1, h02 = 1, h12 = 1.5) |
|
| 25 |
#' getOneClinicalTrial( |
|
| 26 |
#' nPat = c(30, 20, 30), transitionByArm = list(transition1, transition2, transition3), |
|
| 27 |
#' dropout = list(rate = 0, time = 12), |
|
| 28 |
#' accrual = list(param = "time", value = 0) |
|
| 29 |
#' ) |
|
| 30 |
getOneClinicalTrial <- function(nPat, transitionByArm, |
|
| 31 |
dropout = list(rate = 0, time = 12), |
|
| 32 |
accrual = list(param = "time", value = 0)) {
|
|
| 33 | 3105x |
assert_list(transitionByArm) |
| 34 | 3105x |
nPat <- as.integer(nPat) |
| 35 | 3105x |
nArm <- length(transitionByArm) |
| 36 | 3105x |
assert_integer(nPat, |
| 37 | 3105x |
lower = 1, |
| 38 | 3105x |
any.missing = FALSE, |
| 39 | 3105x |
all.missing = FALSE, |
| 40 | 3105x |
len = nArm, |
| 41 |
) |
|
| 42 | 3105x |
assert_list(dropout) |
| 43 | 3105x |
assert_list(accrual) |
| 44 | ||
| 45 |
# Same accrual and dropout parameters for each group? |
|
| 46 | 3105x |
if (is.list(dropout[[1]])) {
|
| 47 | 11x |
assert_list(dropout, len = nArm, types = "list") |
| 48 |
} else {
|
|
| 49 | 3094x |
dropout <- rep(list(dropout), nArm) |
| 50 |
} |
|
| 51 | ||
| 52 | 3105x |
if (is.list(accrual[[1]])) {
|
| 53 | 11x |
assert_list(accrual, len = nArm, types = "list") |
| 54 |
} else {
|
|
| 55 | 3094x |
accrual <- rep(list(accrual), nArm) |
| 56 |
} |
|
| 57 | ||
| 58 |
# Each loop simulates a single trial arm. |
|
| 59 |
# Starting values for the loop. |
|
| 60 | 3105x |
simdata <- NULL |
| 61 | 3105x |
previousPts <- 0 |
| 62 | 3105x |
for (i in seq_len(nArm)) {
|
| 63 | 6205x |
group <- getSimulatedData(nPat[i], transitionByArm[[i]], dropout[[i]], accrual[[i]]) |
| 64 | 6205x |
group$trt <- i |
| 65 | 6205x |
group$id <- group$id + previousPts |
| 66 | 6205x |
simdata <- rbind(simdata, group) |
| 67 | 6205x |
previousPts <- previousPts + nPat[i] |
| 68 |
} |
|
| 69 | 3105x |
if (!any(simdata$from == 1)) {
|
| 70 | 1x |
warning("no progression to death transitions included in the simulated data")
|
| 71 |
} |
|
| 72 | 3105x |
simdata |
| 73 |
} |
|
| 74 | ||
| 75 |
#' Conversion of a Data Set from One Row per Transition to One Row per Patient |
|
| 76 |
#' |
|
| 77 |
#' @param data (`data.frame`)\cr data frame containing entry and exit times of an illness-death model. |
|
| 78 |
#' See [getSimulatedData()] for details. |
|
| 79 |
#' |
|
| 80 |
#' @return This function returns a data set with one row per patient and endpoints PFS and OS. |
|
| 81 |
#' @export |
|
| 82 |
#' |
|
| 83 |
#' @details |
|
| 84 |
#' The output data set contains the following columns: |
|
| 85 |
#' - id (`integer`): patient id. |
|
| 86 |
#' - trt `integer`): treatment id. |
|
| 87 |
#' - PFStime (`numeric`): event time of PFS event. |
|
| 88 |
#' - CensoredPFS (`logical`): censoring indicator for PFS event. |
|
| 89 |
#' - PFSevent (`logical`): event indicator for PFS event. |
|
| 90 |
#' - OStime (`numeric`): event time of OS event. |
|
| 91 |
#' - CensoredOS (`logical`): censoring indicator for OS event. |
|
| 92 |
#' - OSevent (`logical`): event indicator for OS event. |
|
| 93 |
#' - recruitTime (`numeric`): time of recruitment. |
|
| 94 |
#' - OStimeCal (`numeric`): OS event time at calendar time scale. |
|
| 95 |
#' - PFStimeCal (`numeric`): PFS event time at calendar time scale. |
|
| 96 |
#' |
|
| 97 |
#' @examples |
|
| 98 |
#' transition1 <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 99 |
#' transition2 <- exponential_transition(h01 = 1, h02 = 1.3, h12 = 1.7) |
|
| 100 |
#' transition3 <- exponential_transition(h01 = 1.1, h02 = 1, h12 = 1.5) |
|
| 101 |
#' simData <- getOneClinicalTrial( |
|
| 102 |
#' nPat = c(30, 20, 30), transitionByArm = list(transition1, transition2, transition3), |
|
| 103 |
#' dropout = list(rate = 0, time = 12), |
|
| 104 |
#' accrual = list(param = "time", value = 0) |
|
| 105 |
#' ) |
|
| 106 |
#' getDatasetWideFormat(simData) |
|
| 107 |
getDatasetWideFormat <- function(data) {
|
|
| 108 | 90x |
assert_data_frame(data, ncols = 9) |
| 109 | 90x |
assert_subset(c("id", "from", "to", "entry", "exit", "entryAct", "exitAct", "censAct", "trt"), names(data))
|
| 110 | ||
| 111 |
# Recruitment time is the actual entry time of the initial state. |
|
| 112 | 90x |
recruitTime <- subset(data[, c("id", "entryAct")], data$from == 0)
|
| 113 | 90x |
names(recruitTime)[names(recruitTime) == "entryAct"] <- "recruitTime" |
| 114 | ||
| 115 |
# The OS time is the entry time into state 2 or the censoring time whatever occurs first. |
|
| 116 | 90x |
OStime <- subset(data[, c("id", "exit")], data$to == 2 | data$to == "cens")
|
| 117 | 90x |
names(OStime)[names(OStime) == "exit"] <- "OStime" |
| 118 | ||
| 119 |
# The PFS time is the entry time into state 1 or state 2 or the censoring time whatever occurs first. |
|
| 120 | 90x |
PFStime <- subset( |
| 121 | 90x |
data[, c("id", "exit")],
|
| 122 | 90x |
(data$to == 2 & data$from == 0) | (data$to == 1) | (data$to == "cens" & data$from == 0) |
| 123 |
) |
|
| 124 | 90x |
names(PFStime)[names(PFStime) == "exit"] <- "PFStime" |
| 125 | ||
| 126 |
# Add censoring indicators. |
|
| 127 | 90x |
censoredIdsPFS <- data$id[data$to == "cens" & data$from == 0] |
| 128 | 90x |
censoredIdsOS <- data$id[data$to == "cens"] |
| 129 | 90x |
id <- unique(data$id) |
| 130 | 90x |
CensoredOS <- cbind(id = id, CensoredOS = as.integer(id %in% censoredIdsOS)) |
| 131 | 90x |
CensoredPFS <- cbind(id = id, CensoredPFS = as.integer(id %in% censoredIdsPFS)) |
| 132 | ||
| 133 |
# Merge all data sets to one. |
|
| 134 | 90x |
newdata <- unique(data[, c("id", "trt")])
|
| 135 | 90x |
newdata <- merge(x = newdata, y = PFStime, by = "id") |
| 136 | 90x |
newdata <- merge(x = newdata, y = CensoredPFS, by = "id") |
| 137 | ||
| 138 |
# Do we have an observed PFS event? |
|
| 139 | 90x |
newdata$PFSevent <- abs(1 - newdata$CensoredPFS) |
| 140 | 90x |
newdata <- merge(x = newdata, y = OStime, by = "id") |
| 141 | 90x |
newdata <- merge(x = newdata, y = CensoredOS, by = "id") |
| 142 | ||
| 143 |
# Do we have an observed OS event? |
|
| 144 | 90x |
newdata$OSevent <- abs(1 - newdata$CensoredOS) |
| 145 | 90x |
newdata <- merge(x = newdata, y = recruitTime, by = "id") |
| 146 | ||
| 147 |
# Add variables with event times in calendar time. |
|
| 148 | 90x |
newdata$OStimeCal <- newdata$OStime + newdata$recruitTime |
| 149 | 90x |
newdata$PFStimeCal <- newdata$PFStime + newdata$recruitTime |
| 150 | ||
| 151 | 90x |
newdata |
| 152 |
} |
|
| 153 | ||
| 154 |
#' Helper Function for Adding Progress Bar to Trial Simulation |
|
| 155 |
#' |
|
| 156 |
#' @param x (`int`)\cr iteration index within lapply. |
|
| 157 |
#' @param ... parameters transferred to [getOneClinicalTrial()], see [getOneClinicalTrial()] for details. |
|
| 158 |
#' |
|
| 159 |
#' @return This returns the same as [getOneClinicalTrial()], but updates the progress bar. |
|
| 160 |
#' @keywords internal |
|
| 161 |
runTrial <- function(x, pb, ...) {
|
|
| 162 | 3094x |
utils::setTxtProgressBar(pb, x) |
| 163 | 3094x |
getOneClinicalTrial(...) |
| 164 |
} |
|
| 165 | ||
| 166 |
#' Simulation of a Large Number of Oncology Clinical Trials |
|
| 167 |
#' |
|
| 168 |
#' @param nRep (`int`)\cr number of simulated trials. |
|
| 169 |
#' @param ... parameters transferred to [getOneClinicalTrial()], see [getOneClinicalTrial()] for details. |
|
| 170 |
#' @param seed (`int`)\cr random seed used for this simulation. |
|
| 171 |
#' @param datType (`string`)\cr possible values are `1rowTransition` and `1rowPatient`. |
|
| 172 |
#' |
|
| 173 |
#' @return This function returns a list with `nRep` simulated data sets in the format specified by `datType`. |
|
| 174 |
#' See [getDatasetWideFormat()] [getOneClinicalTrial()] for details. |
|
| 175 |
#' @export |
|
| 176 |
#' |
|
| 177 |
#' @examples |
|
| 178 |
#' transition1 <- exponential_transition(h01 = 1.2, h02 = 1.5, h12 = 1.6) |
|
| 179 |
#' transition2 <- exponential_transition(h01 = 1, h02 = 1.3, h12 = 1.7) |
|
| 180 |
#' getClinicalTrials( |
|
| 181 |
#' nRep = 10, nPat = c(20, 20), seed = 1234, datType = "1rowTransition", |
|
| 182 |
#' transitionByArm = list(transition1, transition2), dropout = list(rate = 0.5, time = 12), |
|
| 183 |
#' accrual = list(param = "intensity", value = 7) |
|
| 184 |
#' ) |
|
| 185 |
getClinicalTrials <- function(nRep, ..., seed = 1234, datType = "1rowTransition") {
|
|
| 186 | 19x |
assert_number(nRep, lower = 1) |
| 187 | 19x |
assert_choice(datType, c("1rowTransition", "1rowPatient"))
|
| 188 | ||
| 189 | 19x |
set.seed(seed) |
| 190 | 19x |
cat("Simulating", nRep, "trials:\n")
|
| 191 | 19x |
pb <- utils::txtProgressBar(min = 0, max = nRep, style = 3) |
| 192 |
# getOneClinicalTrial generates a single clinical trial with multiple arms. Generate nRep simulated trials: |
|
| 193 | 19x |
simulatedTrials <- lapply( |
| 194 | 19x |
seq_len(nRep), |
| 195 | 19x |
FUN = function(x, ...) runTrial(x, pb, ...), |
| 196 |
... |
|
| 197 |
) |
|
| 198 | 19x |
close(pb) |
| 199 |
# Final data set format: one row per patient or one row per transition? |
|
| 200 | 19x |
if (datType == "1rowPatient") {
|
| 201 | 14x |
simulatedTrials <- lapply(simulatedTrials, getDatasetWideFormat) |
| 202 |
} |
|
| 203 | 19x |
simulatedTrials |
| 204 |
} |
| 1 |
#' PFS Survival Function from Constant Transition Hazards |
|
| 2 |
#' |
|
| 3 |
#' @inheritParams WeibSurvOS |
|
| 4 |
#' @return This returns the value of PFS survival function at time t. |
|
| 5 |
#' @export |
|
| 6 |
#' |
|
| 7 |
#' @examples |
|
| 8 |
#' ExpSurvPFS(c(1:5), 0.2, 0.4) |
|
| 9 |
ExpSurvPFS <- function(t, h01, h02) {
|
|
| 10 | 800x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 11 | 800x |
assert_positive_number(h01, zero_ok = TRUE) |
| 12 | 800x |
assert_positive_number(h02, zero_ok = TRUE) |
| 13 | ||
| 14 | 800x |
exp(-(h01 + h02) * t) |
| 15 |
} |
|
| 16 | ||
| 17 |
#' OS Survival Function from Constant Transition Hazards |
|
| 18 |
#' |
|
| 19 |
#' @inheritParams WeibSurvOS |
|
| 20 |
#' |
|
| 21 |
#' @return This returns the value of OS survival function at time t. |
|
| 22 |
#' @export |
|
| 23 |
#' |
|
| 24 |
#' @examples |
|
| 25 |
#' ExpSurvOS(c(1:5), 0.2, 0.4, 0.1) |
|
| 26 |
ExpSurvOS <- function(t, h01, h02, h12) {
|
|
| 27 | 130x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 28 | 130x |
assert_positive_number(h01, zero_ok = TRUE) |
| 29 | 130x |
assert_positive_number(h02, zero_ok = TRUE) |
| 30 | 130x |
assert_positive_number(h12, zero_ok = TRUE) |
| 31 | ||
| 32 | 130x |
h012 <- h12 - h01 - h02 |
| 33 | 130x |
ExpSurvPFS(t, h01, h02) + h01 * h012^-1 * (ExpSurvPFS(t, h01, h02) - exp(-h12 * t)) |
| 34 |
} |
|
| 35 | ||
| 36 |
#' PFS Survival Function from Weibull Transition Hazards |
|
| 37 |
#' |
|
| 38 |
#' @inheritParams WeibSurvOS |
|
| 39 |
#' @return This returns the value of PFS survival function at time t. |
|
| 40 |
#' @export |
|
| 41 |
#' @export |
|
| 42 |
#' |
|
| 43 |
#' @examples |
|
| 44 |
#' WeibSurvPFS(c(1:5), 0.2, 0.5, 1.2, 0.9) |
|
| 45 |
WeibSurvPFS <- function(t, h01, h02, p01, p02) {
|
|
| 46 | 173x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 47 | 173x |
assert_positive_number(h01, zero_ok = TRUE) |
| 48 | 173x |
assert_positive_number(h02, zero_ok = TRUE) |
| 49 | 173x |
assert_positive_number(p01) |
| 50 | 173x |
assert_positive_number(p02) |
| 51 | ||
| 52 | 173x |
exp(-h01 * t^p01 - h02 * t^p02) |
| 53 |
} |
|
| 54 | ||
| 55 |
#' Helper for Efficient Integration |
|
| 56 |
#' |
|
| 57 |
#' @param integrand (`function`)\cr to be integrated. |
|
| 58 |
#' @param upper (`numeric`)\cr upper limits of integration. |
|
| 59 |
#' @param ... additional arguments to be passed to `integrand`. |
|
| 60 |
#' |
|
| 61 |
#' @return This function returns for each upper limit the estimates of the integral. |
|
| 62 |
#' @keywords internal |
|
| 63 |
integrateVector <- function(integrand, upper, ...) {
|
|
| 64 | 4175x |
assert_true(all(upper >= 0)) |
| 65 | 4175x |
boundaries <- sort(unique(upper)) |
| 66 | 4175x |
nIntervals <- length(boundaries) |
| 67 | 4175x |
intervals <- mapply( |
| 68 | 4175x |
FUN = function(f, lower, upper, ...) stats::integrate(f, ..., lower, upper)$value, |
| 69 | 4175x |
MoreArgs = list(f = integrand, ...), |
| 70 | 4175x |
lower = c(0, boundaries[-nIntervals]), |
| 71 | 4175x |
upper = boundaries |
| 72 |
) |
|
| 73 | 4175x |
cumIntervals <- cumsum(intervals) |
| 74 | 4175x |
cumIntervals[match(upper, boundaries)] |
| 75 |
} |
|
| 76 | ||
| 77 |
#' Helper Function for `WeibSurvOS()` |
|
| 78 |
#' |
|
| 79 |
#' @param x (`numeric`)\cr variable of integration. |
|
| 80 |
#' @inheritParams WeibSurvOS |
|
| 81 |
#' |
|
| 82 |
#' @return Numeric results of the integrand used to calculate |
|
| 83 |
#' the OS survival function for Weibull transition hazards, see `WeibSurvOS()`. |
|
| 84 |
#' |
|
| 85 |
#' @keywords internal |
|
| 86 |
WeibOSInteg <- function(x, t, h01, h02, h12, p01, p02, p12) {
|
|
| 87 | 4212x |
assert_numeric(x, finite = TRUE, any.missing = FALSE) |
| 88 | 4212x |
assert_numeric(t, finite = TRUE, any.missing = FALSE) |
| 89 | 4212x |
assert_true(test_scalar(x) || identical(length(x), length(t)) || test_scalar(t)) |
| 90 | 4212x |
assert_positive_number(h01, zero_ok = TRUE) |
| 91 | 4212x |
assert_positive_number(h02, zero_ok = TRUE) |
| 92 | 4212x |
assert_positive_number(h12, zero_ok = TRUE) |
| 93 | 4212x |
assert_positive_number(p01) |
| 94 | 4212x |
assert_positive_number(p02) |
| 95 | 4212x |
assert_positive_number(p12) |
| 96 | ||
| 97 | 4212x |
x^(p01 - 1) * exp(-h01 * x^p01 - h02 * x^p02 - h12 * (t^p12 - x^p12)) |
| 98 |
} |
|
| 99 | ||
| 100 |
#' OS Survival Function from Weibull Transition Hazards |
|
| 101 |
#' |
|
| 102 |
#' @param t (`numeric`)\cr study time-points. |
|
| 103 |
#' @param h01 (positive `number`)\cr transition hazard for 0 to 1 transition. |
|
| 104 |
#' @param h02 (positive `number`)\cr transition hazard for 0 to 2 transition. |
|
| 105 |
#' @param h12 (positive `number`)\cr transition hazard for 1 to 2 transition. |
|
| 106 |
#' @param p01 (positive `number`)\cr rate parameter of Weibull distribution for `h01`. |
|
| 107 |
#' @param p02 (positive `number`)\cr rate parameter of Weibull distribution for `h02`. |
|
| 108 |
#' @param p12 (positive `number`)\cr rate parameter of Weibull distribution for `h12`. |
|
| 109 |
#' |
|
| 110 |
#' @return This returns the value of OS survival function at time t. |
|
| 111 |
#' @export |
|
| 112 |
#' |
|
| 113 |
#' @examples WeibSurvOS(c(1:5), 0.2, 0.5, 2.1, 1.2, 0.9, 1) |
|
| 114 |
WeibSurvOS <- function(t, h01, h02, h12, p01, p02, p12) {
|
|
| 115 | 16x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 116 | 16x |
assert_positive_number(h01, zero_ok = TRUE) |
| 117 | 16x |
assert_positive_number(p01) |
| 118 | ||
| 119 | 16x |
WeibSurvPFS(t, h01, h02, p01, p02) + |
| 120 | 16x |
h01 * p01 * |
| 121 | 16x |
sapply(t, function(t) {
|
| 122 | 4161x |
integrateVector(WeibOSInteg, |
| 123 | 4161x |
upper = t, |
| 124 | 4161x |
t = t, |
| 125 | 4161x |
h01 = h01, |
| 126 | 4161x |
h02 = h02, |
| 127 | 4161x |
h12 = h12, |
| 128 | 4161x |
p01 = p01, |
| 129 | 4161x |
p02 = p02, |
| 130 | 4161x |
p12 = p12 |
| 131 |
) |
|
| 132 |
}) |
|
| 133 |
} |
|
| 134 | ||
| 135 |
#' Cumulative Hazard for Piecewise Constant Hazards |
|
| 136 |
#' |
|
| 137 |
#' @param t (`numeric`)\cr study time-points. |
|
| 138 |
#' @param haz (`numeric vector`)\cr constant transition hazards. |
|
| 139 |
#' @param pw (`numeric vector`)\cr time intervals for the piecewise constant hazards. |
|
| 140 |
#' |
|
| 141 |
#' @return This returns the value of cumulative hazard at time t. |
|
| 142 |
#' @export |
|
| 143 |
#' |
|
| 144 |
#' @examples |
|
| 145 |
#' pwA(1:5, c(0.5, 0.9), c(0, 4)) |
|
| 146 |
pwA <- function(t, haz, pw) {
|
|
| 147 | 12581x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 148 | 12581x |
assert_numeric(haz, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 149 | 12581x |
assert_intervals(pw, length(haz)) |
| 150 | ||
| 151 |
# Time intervals: time-points + cut points. |
|
| 152 | 12581x |
pw_new <- c(pw[pw < max(t)]) |
| 153 | 12581x |
pw_time <- sort(unique(c(pw_new, t))) |
| 154 | 12581x |
haz_all <- getPWCHazard(haz, pw, pw_time) |
| 155 | ||
| 156 |
# Cumulative hazard. |
|
| 157 | 12581x |
i <- 1:(length(pw_time) - 1) |
| 158 | 12581x |
dA_pw <- haz_all[i] * (pw_time[i + 1] - pw_time[i]) |
| 159 | 12581x |
A_pw <- c(0, cumsum(dA_pw)) |
| 160 | ||
| 161 |
# Match result with input. |
|
| 162 | 12581x |
A_pw[match(t, pw_time)] |
| 163 |
} |
|
| 164 | ||
| 165 |
#' PFS Survival Function from Piecewise Constant Hazards |
|
| 166 |
#' |
|
| 167 |
#' @inheritParams PWCsurvOS |
|
| 168 |
#' |
|
| 169 |
#' @return This returns the value of PFS survival function at time t. |
|
| 170 |
#' @export |
|
| 171 |
#' |
|
| 172 |
#' @examples |
|
| 173 |
#' PWCsurvPFS(1:5, c(0.3, 0.5), c(0.5, 0.8), c(0, 4), c(0, 8)) |
|
| 174 |
PWCsurvPFS <- function(t, h01, h02, pw01, pw02) {
|
|
| 175 | 56x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 176 | 56x |
assert_numeric(h01, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 177 | 56x |
assert_numeric(h02, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 178 | ||
| 179 | 56x |
assert_intervals(pw01, length(h01)) |
| 180 | 56x |
assert_intervals(pw02, length(h02)) |
| 181 | ||
| 182 | 56x |
A01 <- pwA(t, h01, pw01) |
| 183 | 56x |
A02 <- pwA(t, h02, pw02) |
| 184 | ||
| 185 | 56x |
exp(-A01 - A02) |
| 186 |
} |
|
| 187 | ||
| 188 |
#' Helper Function of `PWCsurvOS()` |
|
| 189 |
#' |
|
| 190 |
#' @param x (`numeric`)\cr variable of integration. |
|
| 191 |
#' @inheritParams PWCsurvOS |
|
| 192 |
#' |
|
| 193 |
#' @return Numeric results of the integrand used to calculate |
|
| 194 |
#' the OS survival function for piecewise constant transition hazards, see `PWCsurvOS`. |
|
| 195 |
#' |
|
| 196 |
#' @keywords internal |
|
| 197 |
PwcOSInt <- function(x, t, h01, h02, h12, pw01, pw02, pw12) {
|
|
| 198 | 30x |
PWCsurvPFS(x, h01, h02, pw01, pw02) * |
| 199 | 30x |
getPWCHazard(h01, pw01, x) * |
| 200 | 30x |
exp(pwA(x, h12, pw12) - pwA(t, h12, pw12)) |
| 201 |
} |
|
| 202 | ||
| 203 |
#' OS Survival Function from Piecewise Constant Hazards |
|
| 204 |
#' |
|
| 205 |
#' @param t (`numeric`)\cr study time-points. |
|
| 206 |
#' @param h01 (`numeric vector`)\cr constant transition hazards for 0 to 1 transition. |
|
| 207 |
#' @param h02 (`numeric vector`)\cr constant transition hazards for 0 to 2 transition. |
|
| 208 |
#' @param h12 (`numeric vector`)\cr constant transition hazards for 1 to 2 transition. |
|
| 209 |
#' @param pw01 (`numeric vector`)\cr time intervals for the piecewise constant hazards `h01`. |
|
| 210 |
#' @param pw02 (`numeric vector`)\cr time intervals for the piecewise constant hazards `h02`. |
|
| 211 |
#' @param pw12 (`numeric vector`)\cr time intervals for the piecewise constant hazards `h12`. |
|
| 212 |
#' |
|
| 213 |
#' @return This returns the value of OS survival function at time t. |
|
| 214 |
#' @export |
|
| 215 |
#' |
|
| 216 |
#' @examples |
|
| 217 |
#' PWCsurvOS(1:5, c(0.3, 0.5), c(0.5, 0.8), c(0.7, 1), c(0, 4), c(0, 8), c(0, 3)) |
|
| 218 |
PWCsurvOS <- function(t, h01, h02, h12, pw01, pw02, pw12) {
|
|
| 219 | 9x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 220 | 9x |
assert_numeric(h01, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 221 | 9x |
assert_numeric(h02, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 222 | 9x |
assert_numeric(h12, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 223 | 9x |
assert_intervals(pw01, length(h01)) |
| 224 | 9x |
assert_intervals(pw02, length(h02)) |
| 225 | 9x |
assert_intervals(pw12, length(h12)) |
| 226 | ||
| 227 |
# Assemble unique time points and corresponding hazard values once. |
|
| 228 | 9x |
unique_pw_times <- sort(unique(c(pw01, pw02, pw12))) |
| 229 | 9x |
h01_at_times <- getPWCHazard(h01, pw01, unique_pw_times) |
| 230 | 9x |
h02_at_times <- getPWCHazard(h02, pw02, unique_pw_times) |
| 231 | 9x |
h12_at_times <- getPWCHazard(h12, pw12, unique_pw_times) |
| 232 | ||
| 233 |
# We work for the integral parts with sorted and unique time points. |
|
| 234 | 9x |
t_sorted <- sort(unique(t)) |
| 235 | 9x |
t_sorted_zero <- c(0, t_sorted) |
| 236 | 9x |
n_t <- length(t_sorted) |
| 237 | 9x |
int_sums <- numeric(n_t) |
| 238 | 9x |
cum_haz_12 <- pwA(t_sorted, h12, pw12) |
| 239 | 9x |
for (i in seq_len(n_t)) {
|
| 240 | 4123x |
t_start <- t_sorted_zero[i] |
| 241 | 4123x |
t_end <- t_sorted_zero[i + 1] |
| 242 |
# Determine the indices of the time intervals we are working with here. |
|
| 243 | 4123x |
start_index <- findInterval(t_start, unique_pw_times) |
| 244 |
# We use here left.open = TRUE, so that when t_end is identical to a change point, |
|
| 245 |
# then we don't need an additional part. |
|
| 246 | 4123x |
end_index <- max(findInterval(t_end, unique_pw_times, left.open = TRUE), 1L) |
| 247 | 4123x |
index_bounds <- start_index:end_index |
| 248 |
# Determine corresponding time intervals. |
|
| 249 | 4123x |
time_bounds <- if (length(index_bounds) == 1L) {
|
| 250 | 4116x |
rbind(c(t_start, t_end)) |
| 251 | 4123x |
} else if (length(index_bounds) == 2L) {
|
| 252 | 5x |
rbind( |
| 253 | 5x |
c(t_start, unique_pw_times[end_index]), |
| 254 | 5x |
c(unique_pw_times[end_index], t_end) |
| 255 |
) |
|
| 256 |
} else {
|
|
| 257 | 2x |
n_ind_bounds <- length(index_bounds) |
| 258 | 2x |
rbind( |
| 259 | 2x |
c(t_start, unique_pw_times[start_index + 1L]), |
| 260 | 2x |
cbind( |
| 261 | 2x |
unique_pw_times[index_bounds[-c(1, n_ind_bounds)]], |
| 262 | 2x |
unique_pw_times[index_bounds[-c(1, 2)]] |
| 263 |
), |
|
| 264 | 2x |
c(unique_pw_times[end_index], t_end) |
| 265 |
) |
|
| 266 |
} |
|
| 267 | 4123x |
for (j in seq_along(index_bounds)) {
|
| 268 | 4133x |
time_j <- time_bounds[j, 1] |
| 269 | 4133x |
time_jp1 <- time_bounds[j, 2] |
| 270 | 4133x |
intercept_a <- pwA(time_j, h12, pw12) - pwA(time_j, h01, pw01) - pwA(time_j, h02, pw02) |
| 271 | 4133x |
slope_b <- h12_at_times[index_bounds[j]] - h01_at_times[index_bounds[j]] - h02_at_times[index_bounds[j]] |
| 272 | 4133x |
h01_j <- h01_at_times[index_bounds[j]] |
| 273 | 4133x |
int_js <- if (slope_b != 0) {
|
| 274 | 4126x |
h01_j / slope_b * exp(intercept_a) * |
| 275 | 4126x |
(exp(slope_b * (time_jp1 - time_j) - cum_haz_12[i:n_t]) - exp(-cum_haz_12[i:n_t])) |
| 276 |
} else {
|
|
| 277 | 7x |
h01_j * exp(intercept_a - cum_haz_12[i:n_t]) * (time_jp1 - time_j) |
| 278 |
} |
|
| 279 | 4133x |
int_sums[i:n_t] <- int_sums[i:n_t] + int_js |
| 280 |
} |
|
| 281 |
} |
|
| 282 | ||
| 283 |
# Match back to all time points, |
|
| 284 |
# which might not be unique or ordered. |
|
| 285 | 9x |
int_sums <- int_sums[match(t, t_sorted)] |
| 286 | 9x |
result <- PWCsurvPFS(t, h01, h02, pw01, pw02) + int_sums |
| 287 | ||
| 288 |
# Cap at 1 in a safe way - i.e. first check. |
|
| 289 | 9x |
assert_numeric(result, finite = TRUE, any.missing = FALSE) |
| 290 | 9x |
above_one <- result > 1 |
| 291 | 9x |
if (any(above_one)) {
|
| 292 | 1x |
assert_true(all((result[above_one] - 1) < sqrt(.Machine$double.eps))) |
| 293 | 1x |
result[above_one] <- 1 |
| 294 |
} |
|
| 295 | 9x |
result |
| 296 |
} |
|
| 297 | ||
| 298 |
#' Helper Function for Single Quantile for OS Survival Function |
|
| 299 |
#' |
|
| 300 |
#' @param q (`number`)\cr single quantile at which to compute event time. |
|
| 301 |
#' @inheritParams ExpQuantOS |
|
| 302 |
#' |
|
| 303 |
#' @return Single time t such that the OS survival function at t equals q. |
|
| 304 |
#' @keywords internal |
|
| 305 |
singleExpQuantOS <- function(q, h01, h02, h12) {
|
|
| 306 | 5x |
assert_number(q, finite = TRUE, lower = 0, upper = 1) |
| 307 | 5x |
toroot <- function(x) {
|
| 308 | 81x |
return(q - ExpSurvOS(t = x, h01, h02, h12)) |
| 309 |
} |
|
| 310 | 5x |
stats::uniroot( |
| 311 | 5x |
toroot, |
| 312 | 5x |
interval = c(0, 10^3), |
| 313 | 5x |
extendInt = "yes" |
| 314 | 5x |
)$root |
| 315 |
} |
|
| 316 | ||
| 317 |
#' Quantile function for OS survival function induced by an illness-death model |
|
| 318 |
#' |
|
| 319 |
#' @param q (`numeric`)\cr quantile(s) at which to compute event time (q = 1 / 2 corresponds to median). |
|
| 320 |
#' @param h01 (`numeric vector`)\cr constant transition hazards for 0 to 1 transition. |
|
| 321 |
#' @param h02 (`numeric vector`)\cr constant transition hazards for 0 to 2 transition. |
|
| 322 |
#' @param h12 (`numeric vector`)\cr constant transition hazards for 1 to 2 transition. |
|
| 323 |
#' |
|
| 324 |
#' @return This returns the time(s) t such that the OS survival function at t equals q. |
|
| 325 |
#' @export |
|
| 326 |
#' |
|
| 327 |
#' @examples ExpQuantOS(1 / 2, 0.2, 0.5, 2.1) |
|
| 328 |
ExpQuantOS <- function(q = 1 / 2, h01, h02, h12) {
|
|
| 329 | 2x |
assert_numeric(q, lower = 0, upper = 1, any.missing = FALSE) |
| 330 | 2x |
assert_numeric(h01, lower = 0, any.missing = FALSE) |
| 331 | 2x |
assert_numeric(h02, lower = 0, any.missing = FALSE) |
| 332 | 2x |
assert_numeric(h12, lower = 0, any.missing = FALSE) |
| 333 | ||
| 334 | 2x |
vapply( |
| 335 | 2x |
q, |
| 336 | 2x |
FUN = singleExpQuantOS, |
| 337 | 2x |
FUN.VALUE = numeric(1), |
| 338 | 2x |
h01 = h01, |
| 339 | 2x |
h02 = h02, |
| 340 | 2x |
h12 = h12 |
| 341 |
) |
|
| 342 |
} |
| 1 |
#' Transition Hazards for Exponential Event Times |
|
| 2 |
#' |
|
| 3 |
#' This creates a list with class `TransitionParameters` containing |
|
| 4 |
#' hazards, time intervals and Weibull rates for exponential event times |
|
| 5 |
#' in an illness-death model. |
|
| 6 |
#' |
|
| 7 |
#' @param h01 (positive `number`)\cr transition hazard for 0 to 1 transition. |
|
| 8 |
#' @param h02 (positive `number`)\cr transition hazard for 0 to 2 transition. |
|
| 9 |
#' @param h12 (positive `number`)\cr transition hazard for 1 to 2 transition. |
|
| 10 |
#' |
|
| 11 |
#' @return List with elements `hazards`, `intervals`, `weibull_rates` and `family` |
|
| 12 |
#' (exponential). |
|
| 13 |
#' @export |
|
| 14 |
#' |
|
| 15 |
#' @examples |
|
| 16 |
#' exponential_transition(1, 1.6, 0.3) |
|
| 17 |
exponential_transition <- function(h01 = 1, h02 = 1, h12 = 1) {
|
|
| 18 | 137x |
assert_positive_number(h01, zero_ok = TRUE) |
| 19 | 136x |
assert_positive_number(h02, zero_ok = TRUE) |
| 20 | 136x |
assert_positive_number(h12, zero_ok = TRUE) |
| 21 | 136x |
structure( |
| 22 | 136x |
list( |
| 23 | 136x |
hazards = list(h01 = h01, h02 = h02, h12 = h12), |
| 24 | 136x |
intervals = list(pw01 = 0, pw02 = 0, pw12 = 0), |
| 25 | 136x |
weibull_rates = list(p01 = 1, p02 = 1, p12 = 1), |
| 26 | 136x |
family = "exponential" |
| 27 |
), |
|
| 28 | 136x |
class = c("ExponentialTransition", "TransitionParameters")
|
| 29 |
) |
|
| 30 |
} |
|
| 31 | ||
| 32 |
#' Transition Hazards for Piecewise Exponential Event Times |
|
| 33 |
#' |
|
| 34 |
#' This creates a list with class `TransitionParameters` containing |
|
| 35 |
#' hazards, time intervals and Weibull rates for piecewise exponential event times |
|
| 36 |
#' in an illness-death model. |
|
| 37 |
#' |
|
| 38 |
#' @param h01 (`numeric vector`)\cr constant transition hazards for 0 to 1 transition |
|
| 39 |
#' @param h02 (`numeric vector`)\cr constant transition hazards for 0 to 2 transition |
|
| 40 |
#' @param h12 (`numeric vector`)\cr constant transition hazards for 1 to 2 transition |
|
| 41 |
#' @param pw01 (`numeric vector`)\cr time intervals for the piecewise constant hazards `h01` |
|
| 42 |
#' @param pw02 (`numeric vector`)\cr time intervals for the piecewise constant hazards `h02` |
|
| 43 |
#' @param pw12 (`numeric vector`)\cr time intervals for the piecewise constant hazards `h12` |
|
| 44 |
#' |
|
| 45 |
#' @return List with elements `hazards`, `intervals`, `weibull_rates` and `family` |
|
| 46 |
#' (piecewise exponential). |
|
| 47 |
#' @export |
|
| 48 |
#' |
|
| 49 |
#' @examples |
|
| 50 |
#' piecewise_exponential( |
|
| 51 |
#' h01 = c(1, 1, 1), h02 = c(1.5, 0.5, 1), h12 = c(1, 1, 1), |
|
| 52 |
#' pw01 = c(0, 3, 8), pw02 = c(0, 6, 7), pw12 = c(0, 8, 9) |
|
| 53 |
#' ) |
|
| 54 |
piecewise_exponential <- function(h01, h02, h12, pw01, pw02, pw12) {
|
|
| 55 | 15x |
assert_numeric(h01, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 56 | 14x |
assert_numeric(h02, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 57 | 14x |
assert_numeric(h12, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 58 | ||
| 59 | 14x |
assert_intervals(pw01, length(h01)) |
| 60 | 13x |
assert_intervals(pw02, length(h02)) |
| 61 | 12x |
assert_intervals(pw12, length(h12)) |
| 62 | ||
| 63 | 12x |
structure( |
| 64 | 12x |
list( |
| 65 | 12x |
hazards = list(h01 = h01, h02 = h02, h12 = h12), |
| 66 | 12x |
intervals = list(pw01 = pw01, pw02 = pw02, pw12 = pw12), |
| 67 | 12x |
weibull_rates = list(p01 = 1, p02 = 1, p12 = 1), |
| 68 | 12x |
family = "piecewise exponential" |
| 69 |
), |
|
| 70 | 12x |
class = c("PWCTransition", "TransitionParameters")
|
| 71 |
) |
|
| 72 |
} |
|
| 73 | ||
| 74 |
#' Transition Hazards for Weibull Distributed Event Times |
|
| 75 |
#' |
|
| 76 |
#' This creates a list with class `TransitionParameters` containing |
|
| 77 |
#' hazards, time intervals and Weibull rates for Weibull distributed event times |
|
| 78 |
#' in an illness-death model. |
|
| 79 |
#' |
|
| 80 |
#' @param h01 (positive `number`)\cr transition hazard for 0 to 1 transition |
|
| 81 |
#' @param h02 (positive `number`)\cr transition hazard for 0 to 2 transition |
|
| 82 |
#' @param h12 (positive `number`)\cr transition hazard for 1 to 2 transition |
|
| 83 |
#' @param p01 (positive `number`)\cr rate parameter of Weibull distribution for `h01` |
|
| 84 |
#' @param p02 (positive `number`)\cr rate parameter of Weibull distribution for `h02` |
|
| 85 |
#' @param p12 (positive `number`)\cr rate parameter of Weibull distribution for `h12` |
|
| 86 |
#' |
|
| 87 |
#' @return List with elements `hazards`, `intervals`, `weibull_rates` and `family` |
|
| 88 |
#' (Weibull). |
|
| 89 |
#' @export |
|
| 90 |
#' |
|
| 91 |
#' @examples |
|
| 92 |
#' weibull_transition(h01 = 1, h02 = 1.3, h12 = 0.5, p01 = 1.2, p02 = 1.3, p12 = 0.5) |
|
| 93 |
weibull_transition <- function(h01 = 1, h02 = 1, h12 = 1, p01 = 1, p02 = 1, p12 = 1) {
|
|
| 94 | 236x |
assert_positive_number(h01, zero_ok = TRUE) |
| 95 | 236x |
assert_positive_number(h02, zero_ok = TRUE) |
| 96 | 235x |
assert_positive_number(h12, zero_ok = TRUE) |
| 97 | 235x |
assert_positive_number(p01) |
| 98 | 235x |
assert_positive_number(p02) |
| 99 | 235x |
assert_positive_number(p12) |
| 100 | 234x |
structure( |
| 101 | 234x |
list( |
| 102 | 234x |
hazards = list(h01 = h01, h02 = h02, h12 = h12), |
| 103 | 234x |
weibull_rates = list(p01 = p01, p02 = p02, p12 = p12), |
| 104 | 234x |
intervals = list(pw01 = 0, pw02 = 0, pw12 = 0), |
| 105 | 234x |
family = "Weibull" |
| 106 |
), |
|
| 107 | 234x |
class = c("WeibullTransition", "TransitionParameters")
|
| 108 |
) |
|
| 109 |
} |
| 1 |
#' OS Hazard Function from Constant Transition Hazards |
|
| 2 |
#' |
|
| 3 |
#' @param t (`numeric`)\cr study time-points. |
|
| 4 |
#' @param h01 (positive `number`)\cr transition hazard for 0 to 1 transition. |
|
| 5 |
#' @param h02 (positive `number`)\cr transition hazard for 0 to 2 transition. |
|
| 6 |
#' @param h12 (positive `number`)\cr transition hazard for 1 to 2 transition. |
|
| 7 |
#' |
|
| 8 |
#' @return This returns the value of the OS hazard function at time t. |
|
| 9 |
#' @export |
|
| 10 |
#' |
|
| 11 |
#' @examples |
|
| 12 |
#' ExpHazOS(c(1:5), 0.2, 1.1, 0.8) |
|
| 13 |
ExpHazOS <- function(t, h01, h02, h12) {
|
|
| 14 | 17x |
assert_numeric(t, lower = 0, any.missing = FALSE) |
| 15 | 17x |
assert_positive_number(h01) |
| 16 | 17x |
assert_positive_number(h02) |
| 17 | 17x |
assert_positive_number(h12) |
| 18 | ||
| 19 | 17x |
h012 <- h12 - h01 - h02 |
| 20 | 17x |
((h12 - h02) * (h01 + h02) - h01 * h12 * exp(-h012 * t)) / ((h12 - h02) - h01 * exp(-h012 * t)) |
| 21 |
} |
|
| 22 | ||
| 23 |
#' Helper Function for `avgHRExpOS()` |
|
| 24 |
#' |
|
| 25 |
#' It is an integrand of the form OS hazard function with intensities h01, h02, h12 |
|
| 26 |
#' at time point t multiplied with a weighted product of the two OS Survival functions |
|
| 27 |
#' at t (one for intensities h0 and one for h1). |
|
| 28 |
#' |
|
| 29 |
#' @param x (`numeric`)\cr variable of integration. |
|
| 30 |
#' @param h01 (positive `number`)\cr transition hazard for 0 to 1 transition. |
|
| 31 |
#' @param h02 (positive `number`)\cr transition hazard for 0 to 2 transition. |
|
| 32 |
#' @param h12 (positive `number`)\cr transition hazard for 1 to 2 transition. |
|
| 33 |
#' @param h0 (`list`)\cr transition parameters for the first treatment group. |
|
| 34 |
#' @param h1 (`list`)\cr transition parameters for the second treatment group. |
|
| 35 |
#' @param alpha (`number`)\cr weight parameter, see [avgHRExpOS()]. |
|
| 36 |
#' @return This returns the value of the integrand used to calculate |
|
| 37 |
#' the average hazard ratio for constant transition hazards, see [avgHRExpOS()]. |
|
| 38 |
#' @export |
|
| 39 |
#' |
|
| 40 |
#' @examples |
|
| 41 |
#' h0 <- list(h01 = 0.18, h02 = 0.06, h12 = 0.17) |
|
| 42 |
#' h1 <- list(h01 = 0.23, h02 = 0.07, h12 = 0.19) |
|
| 43 |
#' avgHRIntegExpOS(x = 5, h01 = 0.2, h02 = 0.5, h12 = 0.7, h0 = h0, h1 = h1, alpha = 0.5) |
|
| 44 |
avgHRIntegExpOS <- function(x, h01, h02, h12, h0, h1, alpha) {
|
|
| 45 | 14x |
assert_positive_number(alpha) |
| 46 | ||
| 47 | 14x |
weightedSurv <- (ExpSurvOS(t = x, h01 = h0$h01, h02 = h0$h02, h12 = h0$h12) * |
| 48 | 14x |
ExpSurvOS(t = x, h01 = h1$h01, h02 = h1$h02, h12 = h1$h12))^alpha |
| 49 | 14x |
ExpHazOS(x, h01, h02, h12) * weightedSurv |
| 50 |
} |
|
| 51 | ||
| 52 |
#' Average OS Hazard Ratio from Constant Transition Hazards |
|
| 53 |
#' |
|
| 54 |
#' @param transitionByArm (`list`)\cr transition parameters for each treatment group. |
|
| 55 |
#' See [exponential_transition()], [piecewise_exponential()] and [weibull_transition()] for details. |
|
| 56 |
#' @param alpha (`number`)\cr assigns weights to time points, where values higher than 0.5 assign greater weight |
|
| 57 |
#' to earlier times and values lower than 0.5 assign greater weight to later times. |
|
| 58 |
#' @param upper (`number`)\cr upper (time) limit of integration. |
|
| 59 |
#' |
|
| 60 |
#' @return This returns the value of the average hazard ratio. |
|
| 61 |
#' @export |
|
| 62 |
#' |
|
| 63 |
#' @examples |
|
| 64 |
#' transitionTrt <- exponential_transition(h01 = 0.18, h02 = 0.06, h12 = 0.17) |
|
| 65 |
#' transitionCtl <- exponential_transition(h01 = 0.23, h02 = 0.07, h12 = 0.19) |
|
| 66 |
#' transitionList <- list(transitionCtl, transitionTrt) |
|
| 67 |
#' avgHRExpOS(transitionByArm = transitionList, alpha = 0.5, upper = 100) |
|
| 68 |
avgHRExpOS <- function(transitionByArm, alpha = 0.5, upper = Inf) {
|
|
| 69 | 2x |
assert_list(transitionByArm, len = 2) |
| 70 | 2x |
assert_class(transitionByArm[[1]], "TransitionParameters") |
| 71 | 2x |
assert_class(transitionByArm[[2]], "TransitionParameters") |
| 72 | 2x |
assert_positive_number(alpha) |
| 73 | 2x |
assert_positive_number(upper) |
| 74 | ||
| 75 | 2x |
h0 <- transitionByArm[[1]]$hazard |
| 76 | 2x |
h1 <- transitionByArm[[2]]$hazard |
| 77 | ||
| 78 | 2x |
num <- stats::integrate(avgHRIntegExpOS, |
| 79 | 2x |
lower = 0, upper = upper, h01 = h1$h01, h02 = h1$h02, h12 = h1$h12, |
| 80 | 2x |
h0 = h0, h1 = h1, alpha = alpha |
| 81 | 2x |
)$value |
| 82 | 2x |
denom <- stats::integrate(avgHRIntegExpOS, |
| 83 | 2x |
lower = 0, upper = upper, h01 = h0$h01, h02 = h0$h02, h12 = h0$h12, |
| 84 | 2x |
h0 = h0, h1 = h1, alpha = alpha |
| 85 | 2x |
)$value |
| 86 | 2x |
num / denom |
| 87 |
} |
| 1 |
#' Piecewise Exponentially Distributed Event Times |
|
| 2 |
#' |
|
| 3 |
#' This returns event times with a distribution resulting from piece-wise constant hazards |
|
| 4 |
#' using the inversion method. |
|
| 5 |
#' |
|
| 6 |
#' @param U (`numeric`)\cr uniformly distributed random variables. |
|
| 7 |
#' @param haz (`numeric`)\cr piecewise constant hazard. |
|
| 8 |
#' @param pw (`numeric`)\cr time intervals for the piecewise constant hazard. |
|
| 9 |
#' @param t_0 (`numeric`)\cr the starting times. |
|
| 10 |
#' |
|
| 11 |
#' @return This returns a vector with event times. |
|
| 12 |
#' @export |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' getPCWDistr(U = runif(3), haz = c(1.1, 0.5, 0.4), pw = c(0, 7, 10), t_0 = c(0, 1, 4.2)) |
|
| 16 |
getPCWDistr <- function(U, haz, pw, t_0) {
|
|
| 17 | 4013x |
assert_numeric(U, lower = 0, upper = 1, any.missing = FALSE, all.missing = FALSE) |
| 18 | 4013x |
assert_numeric(haz, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 19 | 4013x |
assert_intervals(pw, length(haz)) |
| 20 | 4013x |
assert_numeric(t_0, lower = 0, len = length(U), any.missing = FALSE, all.missing = FALSE) |
| 21 | ||
| 22 | 4013x |
N <- length(U) |
| 23 | 4013x |
t1 <- rep(NA, N) # Initialize event times. |
| 24 | 4013x |
n2 <- length(haz) |
| 25 | 4013x |
for (kk in seq_len(N)) {
|
| 26 |
# Shift if t_0 !=0. |
|
| 27 | 556838x |
cuts_temp <- c(t_0[kk], pw[pw > t_0[kk]]) |
| 28 | 556838x |
cuts_temp <- cuts_temp - t_0[kk] |
| 29 |
# Number of cut-points after shift. |
|
| 30 | 556838x |
n <- length(cuts_temp) |
| 31 |
# Number of hazards to remove for shift (t_0 > times). |
|
| 32 | 556838x |
remov <- n2 - n |
| 33 | 556838x |
haz_temp <- haz[(remov + 1):n2] |
| 34 | 556838x |
LogU <- log(1 - U[kk]) |
| 35 | ||
| 36 | 556838x |
t1[kk] <- if (n != 1) {
|
| 37 | 556833x |
PCWInversionMethod(haz = haz_temp, pw = cuts_temp, LogU = LogU) |
| 38 | 556838x |
} else if (n == 1) { # I.e. exponential distribution.
|
| 39 | 5x |
-LogU / haz_temp |
| 40 |
} |
|
| 41 |
} |
|
| 42 | 4013x |
t1 |
| 43 |
} |
|
| 44 | ||
| 45 |
#' Single Piecewise Exponentially Distributed Event Time |
|
| 46 |
#' |
|
| 47 |
#' This returns an event time with a distribution resulting from piece-wise constant hazards |
|
| 48 |
#' using the inversion method. |
|
| 49 |
#' |
|
| 50 |
#' @param pw (`numeric`)\cr time intervals for the piecewise constant hazard. |
|
| 51 |
#' @param haz (`numeric`)\cr piecewise constant hazard. |
|
| 52 |
#' @param LogU (`numeric`)\cr transformed uniformly distributed random variables (log(1-U)). |
|
| 53 |
#' |
|
| 54 |
#' @return This returns one single event time. |
|
| 55 |
#' @export |
|
| 56 |
#' |
|
| 57 |
#' @examples |
|
| 58 |
#' PCWInversionMethod(haz = c(1.1, 0.5, 0.4), pw = c(0, 7, 10), LogU = log(1 - runif(1))) |
|
| 59 |
PCWInversionMethod <- function(haz, pw, LogU) {
|
|
| 60 | 556834x |
assert_numeric(haz, lower = 0, any.missing = FALSE, all.missing = FALSE) |
| 61 | 556834x |
assert_intervals(pw, length(haz)) |
| 62 | 556834x |
assert_number(LogU) |
| 63 | ||
| 64 | 556834x |
n <- length(pw) |
| 65 | 556834x |
t1 <- 0 |
| 66 |
# Determine sum of alpha*time-interval for all i. |
|
| 67 | 556834x |
dt <- pw[2:n] - pw[1:(n - 1)] |
| 68 | ||
| 69 |
# Helping matrix. |
|
| 70 | 556834x |
tempMatrix <- matrix(0, nrow = n, ncol = n - 1) |
| 71 | 556834x |
tempMatrix[lower.tri(tempMatrix)] <- 1 |
| 72 | ||
| 73 | 556834x |
sumA <- -as.vector(tempMatrix %*% (haz[1:(n - 1)] * dt)) |
| 74 |
# Find the appropriate time interval. |
|
| 75 | 556834x |
rev_index <- findInterval(LogU, rev(sumA), rightmost.closed = FALSE, left.open = TRUE, all.inside = FALSE) |
| 76 |
# Use reverse index. |
|
| 77 | 556834x |
index <- n - rev_index |
| 78 |
# Calculate event time. |
|
| 79 | 556834x |
pw[index] + (sumA[index] - LogU) / haz[index] |
| 80 |
} |
| 1 |
#' Piecewise Constant Hazard Values |
|
| 2 |
#' |
|
| 3 |
#' This returns piecewise constant hazard values at specified time points. |
|
| 4 |
#' |
|
| 5 |
#' @param haz (`numeric`)\cr piecewise constant input hazard. |
|
| 6 |
#' @param pw (`numeric`)\cr time intervals for the piecewise constant hazard. |
|
| 7 |
#' @param x (`numeric`)\cr time-points. |
|
| 8 |
#' |
|
| 9 |
#' @return Hazard values at input time-points. |
|
| 10 |
#' @export |
|
| 11 |
#' |
|
| 12 |
#' @examples |
|
| 13 |
#' getPWCHazard(c(1, 1.2, 1.4), c(0, 2, 3), c(1, 4, 6)) |
|
| 14 |
getPWCHazard <- function(haz, pw, x) { # nolint
|
|
| 15 | 38866x |
sapply(x, function(jj) {
|
| 16 | 1283790x |
y <- NULL |
| 17 |
# Find interval and corresponding hazard value for time x[jj]. |
|
| 18 | 1283790x |
for (ii in seq_along(haz)) {
|
| 19 | 3851341x |
if (jj >= pw[ii]) {
|
| 20 | 1475722x |
y <- haz[ii] |
| 21 |
} |
|
| 22 |
} |
|
| 23 | 1283790x |
y |
| 24 |
}) |
|
| 25 |
} |
|
| 26 | ||
| 27 |
#' Sum of Two Piecewise Constant Hazards |
|
| 28 |
#' |
|
| 29 |
#' This returns the sum of two piecewise constant hazards per interval. |
|
| 30 |
#' |
|
| 31 |
#' @param haz1 (`numeric`)\cr first summand (piecewise constant hazard). |
|
| 32 |
#' @param haz2 (`numeric`)\cr second summand (piecewise constant hazard). |
|
| 33 |
#' @param pw1 (`numeric`)\cr time intervals of first summand. |
|
| 34 |
#' @param pw2 (`numeric`)\cr time intervals of second summand. |
|
| 35 |
#' |
|
| 36 |
#' @return List with elements `hazards` and `intervals` for the sum of two piecewise constant hazards. |
|
| 37 |
#' @export |
|
| 38 |
#' |
|
| 39 |
#' @examples |
|
| 40 |
#' getSumPCW(c(1.2, 0.3, 0.6), c(1.2, 0.7, 1), c(0, 8, 9), c(0, 1, 4)) |
|
| 41 |
getSumPCW <- function(haz1, haz2, pw1, pw2) {
|
|
| 42 |
# Get all cutpoints for the intervals. |
|
| 43 | 2008x |
cuts_sum <- unique(sort(c(pw1, pw2))) |
| 44 | 2008x |
haz_sum <- NULL |
| 45 |
# Get sum of hazards for all intervals. |
|
| 46 | 2008x |
for (i in seq_along(cuts_sum)) {
|
| 47 | 10026x |
haz_sum[i] <- getPWCHazard(haz1, pw1, cuts_sum[i]) + |
| 48 | 10026x |
getPWCHazard(haz2, pw2, cuts_sum[i]) |
| 49 |
} |
|
| 50 | 2008x |
list( |
| 51 | 2008x |
hazards = haz_sum, |
| 52 | 2008x |
intervals = cuts_sum |
| 53 |
) |
|
| 54 |
} |
| 1 |
#' Assertion for Positive Number |
|
| 2 |
#' |
|
| 3 |
#' @param x what to check. |
|
| 4 |
#' @param zero_ok (`flag`)\cr whether `x` can be zero or not. |
|
| 5 |
#' |
|
| 6 |
#' @return Raises an error if `x` is not a single positive (or non-negative) number. |
|
| 7 |
#' @export |
|
| 8 |
#' |
|
| 9 |
#' @examples |
|
| 10 |
#' assert_positive_number(3.2) |
|
| 11 |
#' assert_positive_number(0, zero_ok = TRUE) |
|
| 12 |
assert_positive_number <- function(x, |
|
| 13 |
zero_ok = FALSE) {
|
|
| 14 | 54926x |
assert_number(x) |
| 15 | 54925x |
assert_flag(zero_ok) |
| 16 | 54925x |
if (zero_ok) {
|
| 17 | 28569x |
assert_true(x >= 0) |
| 18 |
} else {
|
|
| 19 | 26356x |
assert_true(x > 0) |
| 20 |
} |
|
| 21 |
} |
|
| 22 | ||
| 23 |
#' Assertion for vector describing intervals |
|
| 24 |
#' |
|
| 25 |
#' We define an intervals vector to always start with 0, and contain |
|
| 26 |
#' unique ordered time points. |
|
| 27 |
#' |
|
| 28 |
#' @param x what to check. |
|
| 29 |
#' @param y (`count`)\cr required length of `y`. |
|
| 30 |
#' |
|
| 31 |
#' @return Raises an error if `x` is not an intervals vector starting with 0. |
|
| 32 |
#' @export |
|
| 33 |
#' |
|
| 34 |
#' @examples |
|
| 35 |
#' assert_intervals(c(0, 5, 7), 3) |
|
| 36 |
assert_intervals <- function(x, y) {
|
|
| 37 | 573612x |
assert_numeric( |
| 38 | 573612x |
x, |
| 39 | 573612x |
lower = 0, |
| 40 | 573612x |
any.missing = FALSE, |
| 41 | 573612x |
all.missing = FALSE, |
| 42 | 573612x |
len = y, |
| 43 | 573612x |
unique = TRUE, |
| 44 | 573612x |
sorted = TRUE |
| 45 |
) |
|
| 46 | 573607x |
assert_true(x[1] == 0) |
| 47 |
} |