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 |
} |