|
a |
|
b/R/sfn.R |
|
|
1 |
#Code for the predict from parameters Survival functions/ |
|
|
2 |
#functions to be used in the integral when calculating the |
|
|
3 |
#expected event times and times at risk |
|
|
4 |
|
|
|
5 |
|
|
|
6 |
##' A Class containing the Survival Function (for a single arm) |
|
|
7 |
##' used in the integral to calculate event times |
|
|
8 |
##' in predict from parameters. It should not be created by the user |
|
|
9 |
##' but is created for the user (see sfns slot of \code{AnalysisResult}) |
|
|
10 |
##' |
|
|
11 |
##' @slot sfn A function sfn(x) is the function to be included in the events.integrate |
|
|
12 |
##' procedure when using predict from parameters. |
|
|
13 |
##' Specifically sfn(x) = 1 - P(had event [i.e. not dropout] by time x) and when no drop outs |
|
|
14 |
##' this is exactly the survival function. |
|
|
15 |
##' If using dropouts this is not quite the survival function |
|
|
16 |
##' However, the output of LatexSurvivalFn(object) is the survival function. |
|
|
17 |
##' @slot SurvivalFunction The actual survival function (if drop outs/finite followup are used then this will |
|
|
18 |
##' not equal sfn). If finite follow up is used then S(x) = 0 for x > followup |
|
|
19 |
##' @slot pdf The pdf function associated with the survival function. If finite follow up is used |
|
|
20 |
##' then pdf(x) should = 0 for x > followup |
|
|
21 |
##' @slot nullf Logical, TRUE if the object represents NULL (i.e. a survival function for |
|
|
22 |
##' a second arm in a single arm study) |
|
|
23 |
##' @slot lambda The rate parameters for the arm of trial. |
|
|
24 |
##' In a trial with lag, this is the rate parameters for time > T |
|
|
25 |
##' @slot lambdaot If a lag was used then the rate parameters |
|
|
26 |
##' for time < T otherwise NA |
|
|
27 |
##' @slot shape The Weibull shape parameter |
|
|
28 |
##' @slot LagT The lagtime for the survival function (0 for no lag) |
|
|
29 |
##' @slot followup The follow up time for each subject |
|
|
30 |
##' (Inf for studies with no fixed followup) |
|
|
31 |
##' @slot dropout.shape The Weibull shape parameter for the drop out hazard function |
|
|
32 |
##' @slot dropout.lambda The rate parameter for the drop out hazard function = 0 if no dropout |
|
|
33 |
##' |
|
|
34 |
##' @export |
|
|
35 |
setClass("Sfn", |
|
|
36 |
slots=list(sfn="function", |
|
|
37 |
SurvivalFunction="function", |
|
|
38 |
pdf="function", |
|
|
39 |
nullf="logical", |
|
|
40 |
lambda="numeric", |
|
|
41 |
lambdaot="numeric", |
|
|
42 |
shape="numeric", |
|
|
43 |
LagT="numeric", |
|
|
44 |
followup="numeric", |
|
|
45 |
dropout.shape="numeric", |
|
|
46 |
dropout.lambda="numeric" |
|
|
47 |
) |
|
|
48 |
) |
|
|
49 |
|
|
|
50 |
# Constructor for \code{Sfn} object |
|
|
51 |
# @param lambda The rate parameters for the arm of the trial. |
|
|
52 |
# In a trial with lag, this is the rate parameters for time > T |
|
|
53 |
# @param lambdaot If a lag was used then the rate parameters |
|
|
54 |
# for time < T otherwise NA |
|
|
55 |
# @param lag.T The lagtime for the survival function (0 for no lag) |
|
|
56 |
# @param shape The Weibull shape parameter |
|
|
57 |
# @param followup The follow up time for each subject (Inf for studies with no fixed followup) |
|
|
58 |
# @param dropout.shape The Weibull shape parameter for the drop out hazard function |
|
|
59 |
# @param dropout.lambda The rate parameter for the drop out hazard function - 0 |
|
|
60 |
# if no drop out |
|
|
61 |
Sfn <- function(lambda,lambdaot,lag.T,shape,followup,dropout.shape=1,dropout.lambda=0){ |
|
|
62 |
|
|
|
63 |
if(lag.T==0){ |
|
|
64 |
lambdaot <- as.numeric(NA) |
|
|
65 |
} |
|
|
66 |
|
|
|
67 |
#survival function without dropouts or fixed follow up... |
|
|
68 |
f <- if(lag.T==0) |
|
|
69 |
function(x){ |
|
|
70 |
return(exp(-(lambda*x)^shape)) |
|
|
71 |
} |
|
|
72 |
else |
|
|
73 |
function(x){ |
|
|
74 |
ifelse(x<lag.T, exp(-(lambdaot*x)^shape),exp(-(lambda*x)^shape+(lambda^shape-lambdaot^shape)*lag.T^shape)) |
|
|
75 |
} |
|
|
76 |
|
|
|
77 |
#... and its corresponding hazard function |
|
|
78 |
h <- if(lag.T==0) |
|
|
79 |
function(x){ |
|
|
80 |
return(shape*lambda^shape*x^(shape-1)) |
|
|
81 |
} |
|
|
82 |
else |
|
|
83 |
function(x){ |
|
|
84 |
ifelse(x<lag.T,shape*lambdaot^shape*x^(shape-1),shape*lambda^shape*x^(shape-1)) |
|
|
85 |
} |
|
|
86 |
|
|
|
87 |
################################################################ |
|
|
88 |
|
|
|
89 |
#next we calulate the function giving 1- P(having event) which is placed in the sfn slot |
|
|
90 |
#of the object when including dropouts this function is the integrated or cumulative hazard for hazard |
|
|
91 |
#event. |
|
|
92 |
|
|
|
93 |
#The integrand of the cumulative hazard function |
|
|
94 |
f2 <- function(x){h(x)*f(x)*exp(-(x*dropout.lambda)^dropout.shape)} |
|
|
95 |
|
|
|
96 |
#we precompute the value of the integral when there is dropout and lag |
|
|
97 |
lag.T.int <- if(lag.T != 0 && dropout.lambda != 0) integrate(f=f2,lower=0,upper=lag.T)$value else 0 |
|
|
98 |
|
|
|
99 |
#we precompute the value of 1-P(having event by time=follow up) to save calculating the integral |
|
|
100 |
#multiple times |
|
|
101 |
follow.up.int <- if(dropout.lambda!=0 && !is.infinite(followup)) |
|
|
102 |
integrate(f=f2,lower=0,upper=followup)$value else 0 |
|
|
103 |
|
|
|
104 |
|
|
|
105 |
#include drop out and follow up if required for slot sfn (so no longer the survival function) |
|
|
106 |
dropf <- function(x){ |
|
|
107 |
#First deal with case of no dropout |
|
|
108 |
if(dropout.lambda==0){ |
|
|
109 |
return(ifelse(x<followup,f(x),f(followup))) |
|
|
110 |
} |
|
|
111 |
|
|
|
112 |
#next no lag but followup |
|
|
113 |
if(lag.T==0 && !is.infinite(followup)){ |
|
|
114 |
return(sapply(x, function(t){ |
|
|
115 |
ifelse(t < followup, |
|
|
116 |
1-integrate(f=f2,lower=0,upper=t)$value, |
|
|
117 |
1-follow.up.int) #1-P(have event by time t > followup) = 1-P(have event by time followup) |
|
|
118 |
})) |
|
|
119 |
} |
|
|
120 |
|
|
|
121 |
#Next no lag or followup < lag (so no discontinuity) |
|
|
122 |
if(lag.T==0 || followup <= lag.T){ |
|
|
123 |
return(sapply(x,function(t){1-integrate(f=f2,lower=0,upper=min(t,followup))$value})) |
|
|
124 |
} |
|
|
125 |
|
|
|
126 |
#finally case with lag and follow up > lag |
|
|
127 |
sapply(x, function(t){ |
|
|
128 |
ifelse(t < lag.T, |
|
|
129 |
1-integrate(f=f2,lower=0,upper=min(t,followup))$value, |
|
|
130 |
1-integrate(f=f2,lower=lag.T,upper=min(t,followup))$value-lag.T.int #split integral into two parts |
|
|
131 |
) |
|
|
132 |
}) |
|
|
133 |
|
|
|
134 |
} |
|
|
135 |
#the function to be included in the events integral |
|
|
136 |
sfn <- dropf |
|
|
137 |
|
|
|
138 |
######################################## |
|
|
139 |
|
|
|
140 |
#Now we deal with the Survival function |
|
|
141 |
#Add drop outs |
|
|
142 |
S <- function(x){ |
|
|
143 |
if(dropout.lambda==0){ |
|
|
144 |
return(f(x)) |
|
|
145 |
} |
|
|
146 |
f(x)*exp(-(x*dropout.lambda)^dropout.shape) |
|
|
147 |
} |
|
|
148 |
|
|
|
149 |
#and fixed follow ups |
|
|
150 |
#In '<=' the equal sign here is crucial as we need |
|
|
151 |
#to use S(followup) when calculating the atrisk information |
|
|
152 |
SurvivalFunction <- function(x){ifelse(x<=followup,S(x),0)} |
|
|
153 |
|
|
|
154 |
####################################### |
|
|
155 |
|
|
|
156 |
##Next work on pdf |
|
|
157 |
|
|
|
158 |
dropouth <- function(x){ |
|
|
159 |
if(dropout.lambda==0){ |
|
|
160 |
return(0) |
|
|
161 |
} |
|
|
162 |
return((dropout.lambda*dropout.shape)*(dropout.lambda*x)^(dropout.shape-1)) |
|
|
163 |
} |
|
|
164 |
|
|
|
165 |
#pdf is hazard function * survival function |
|
|
166 |
pdfwithdropout <- if(dropout.lambda==0) function(x){S(x)*h(x)} else function(x){S(x)*(h(x)+dropouth(x))} |
|
|
167 |
|
|
|
168 |
##and fixed follow up |
|
|
169 |
pdf <- if(is.infinite(followup)) pdfwithdropout else function(x){ifelse(x<=followup,pdfwithdropout(x),0)} |
|
|
170 |
|
|
|
171 |
new("Sfn",sfn=sfn,nullf=FALSE, |
|
|
172 |
lambda=lambda,lambdaot=lambdaot,LagT=lag.T,shape=shape,followup=followup, |
|
|
173 |
dropout.lambda=dropout.lambda,dropout.shape=dropout.shape,SurvivalFunction=SurvivalFunction,pdf=pdf) |
|
|
174 |
|
|
|
175 |
} |
|
|
176 |
|
|
|
177 |
# Create a Null version of the \code{Sfn} object |
|
|
178 |
# For use as the survival function for the experimental arm |
|
|
179 |
# of a single arm trial |
|
|
180 |
# @return A \code{Sfn} object |
|
|
181 |
NullSfn <- function(){ |
|
|
182 |
f <- function(x){0} |
|
|
183 |
new("Sfn",sfn=f,nullf=TRUE,lambda=0,lambdaot=0,LagT=0,shape=0,followup=0, |
|
|
184 |
dropout.shape=1,dropout.lambda=0,SurvivalFunction=f,pdf=f) |
|
|
185 |
} |
|
|
186 |
|
|
|
187 |
##' Method to output a Latex String of the object |
|
|
188 |
##' |
|
|
189 |
##' @param results Object to output |
|
|
190 |
##' @param \ldots Additional parameters to be passed into the function |
|
|
191 |
##' @param lambda The symbol to be used for the arm's rate |
|
|
192 |
##' parameter, if lagged study then this is a vector of before and after lag |
|
|
193 |
##' symbols. The latex backslash character needs to be escaped |
|
|
194 |
##' @param shape The symbol to be used for Weibull shape parameter |
|
|
195 |
##' @return A latex string of the object (for \code{Sfn} this is the Survival function) |
|
|
196 |
##' @rdname LatexSurvivalFn-methods |
|
|
197 |
##' @name LatexSurvivalFn |
|
|
198 |
##' @export |
|
|
199 |
setGeneric( "LatexSurvivalFn", |
|
|
200 |
def = function( results, ... ) |
|
|
201 |
standardGeneric( "LatexSurvivalFn" )) |
|
|
202 |
|
|
|
203 |
|
|
|
204 |
##' @name LatexSurvivalFn |
|
|
205 |
##' @rdname LatexSurvivalFn-methods |
|
|
206 |
##' @aliases LatexSurvivalFn,Sfn-method |
|
|
207 |
##' @export |
|
|
208 |
setMethod("LatexSurvivalFn", |
|
|
209 |
signature("Sfn"), |
|
|
210 |
function(results,decimalplaces,lambda,shape){ |
|
|
211 |
|
|
|
212 |
#First set the function which will produce the output |
|
|
213 |
if(results@LagT!= 0 && results@shape ==1 ){ |
|
|
214 |
LatexOutput <- function(lambda,shape,followup,dropouttext){ |
|
|
215 |
return(paste("$$ S(t) = \\begin{cases} \\exp(-",lambda[1],"t",dropouttext,") & \\quad \\text{if } t \\le T \\\\ |
|
|
216 |
\\exp(-",lambda[2],"t-(",lambda[1],"-",lambda[2],")T",dropouttext,") & \\quad \\text{if } t > T\\ \\end{cases} $$")) |
|
|
217 |
} |
|
|
218 |
}else if(results@LagT!= 0 && results@shape !=1){ |
|
|
219 |
LatexOutput <- function(lambda,shape,followup,dropouttext){ |
|
|
220 |
return(paste("$$ S(t) = \\begin{cases} \\exp(-(",lambda[1],"t)^",shape,dropouttext,") & \\quad \\text{if } t \\le T \\\\ |
|
|
221 |
\\exp(-(",lambda[2],"t)^",shape,"-(",lambda[1],"^",shape, |
|
|
222 |
"-",lambda[2],"^",shape,")T^",shape,dropouttext,") & \\quad \\text{if } t > T\\ \\end{cases} $$")) |
|
|
223 |
} |
|
|
224 |
}else if(results@LagT == 0 && results@shape ==1){ |
|
|
225 |
LatexOutput <- function(lambda,shape,followup,dropouttext){ |
|
|
226 |
fuptxt <- if(is.infinite(followup)) "" else paste("\\quad (\\text{if } t <",round(followup,decimalplaces),")") |
|
|
227 |
return(paste("$$S(t) = \\exp(-",lambda[1],"t",dropouttext,")",fuptxt,"$$")) |
|
|
228 |
} |
|
|
229 |
}else{ |
|
|
230 |
LatexOutput <- function(lambda,shape,followup,dropouttext){ |
|
|
231 |
fuptxt <- if(is.infinite(followup)) "" else paste("\\quad (\\text{if } t <",round(followup,decimalplaces),")") |
|
|
232 |
return(paste("$$S(t) = \\exp(-(",lambda[1],"t)^",shape,dropouttext,")",fuptxt,"$$")) |
|
|
233 |
} |
|
|
234 |
} |
|
|
235 |
|
|
|
236 |
#Then produce the output |
|
|
237 |
ans <- "" |
|
|
238 |
extra <- "" |
|
|
239 |
|
|
|
240 |
dropouttext <- "" |
|
|
241 |
if(results@dropout.lambda!=0){ |
|
|
242 |
|
|
|
243 |
if(results@dropout.shape==1){ |
|
|
244 |
dropouttext <- paste("-",lambda[3],"t") |
|
|
245 |
} |
|
|
246 |
else{ |
|
|
247 |
dropouttext <- paste("-(",lambda[3],"t)^",shape[2]) |
|
|
248 |
} |
|
|
249 |
} |
|
|
250 |
|
|
|
251 |
|
|
|
252 |
if(results@LagT!=0){ |
|
|
253 |
extra <- paste(",\\:",lambda[2],"=",round(results@lambda,decimalplaces)) |
|
|
254 |
lambda_val <- round(results@lambdaot,decimalplaces) |
|
|
255 |
} |
|
|
256 |
else{ |
|
|
257 |
lambda_val <- round(results@lambda,decimalplaces) |
|
|
258 |
} |
|
|
259 |
|
|
|
260 |
ans <- paste(ans,LatexOutput(lambda,shape=shape[1],followup=results@followup,dropouttext),"\n") |
|
|
261 |
ans <- paste(ans,"$$",lambda[1],"=",lambda_val,extra) |
|
|
262 |
|
|
|
263 |
if(results@shape !=1) { |
|
|
264 |
ans <- paste(ans,",\\:",shape[1],"=",round(results@shape,decimalplaces)) |
|
|
265 |
} |
|
|
266 |
|
|
|
267 |
if(results@dropout.lambda!=0){ |
|
|
268 |
ans <- paste(ans,",\\:",lambda[3],"=",round(results@dropout.lambda,decimalplaces)) |
|
|
269 |
|
|
|
270 |
if(results@dropout.shape!=1){ |
|
|
271 |
ans <- paste(ans,",\\:",shape[2],"=",round(results@dropout.shape,decimalplaces)) |
|
|
272 |
} |
|
|
273 |
} |
|
|
274 |
|
|
|
275 |
ans <- paste(ans,"$$\n") |
|
|
276 |
return(ans) |
|
|
277 |
} |
|
|
278 |
) |
|
|
279 |
|
|
|
280 |
# Calculate the expected time at risk |
|
|
281 |
# |
|
|
282 |
# @param SurvFn sfn object. If nullf slot of sfn object is true |
|
|
283 |
# then 0 time at risk occurs occur (this is used |
|
|
284 |
# when calculating single arm trials) |
|
|
285 |
# @param B Accrual time. |
|
|
286 |
# @param k Non-uniformity of accrual (numeric, 1=uniform). |
|
|
287 |
# @param t A vector of prediction times for which the expected time |
|
|
288 |
# at risk is required |
|
|
289 |
# @return A vector of time at risk at the given times |
|
|
290 |
atrisk.integ <- function( SurvFn, B, k, t ) { |
|
|
291 |
if(SurvFn@nullf){ |
|
|
292 |
return (rep(0,length(t))) |
|
|
293 |
} |
|
|
294 |
|
|
|
295 |
#see the predict from parameters vignette for the three formula calculated here |
|
|
296 |
#and their derivations |
|
|
297 |
|
|
|
298 |
##################### |
|
|
299 |
|
|
|
300 |
#First calculate the amount of time of subjects who are still on the trial at time t |
|
|
301 |
#= integral from 0 to min(t,B) of P(still on trial at t | rec at s)P(rec at s)*time_at_risk ds |
|
|
302 |
#and time_at_risk=t-s |
|
|
303 |
myf1 <- function(s,k,t){(k*s^(k-1)/B^k)*SurvFn@SurvivalFunction(t-s)*(t-s)} |
|
|
304 |
|
|
|
305 |
i1 <- unlist(lapply(t, |
|
|
306 |
function(x){ |
|
|
307 |
if(x==0) 0 else integrate(f=myf1,lower=0,upper=min(x,B),k=k,t=x)$value |
|
|
308 |
})) |
|
|
309 |
|
|
|
310 |
#Next calculate the amount of time of subjects who had an event or dropped out before time t |
|
|
311 |
#This does not include subjects who have dropped out as they were censored at the end of the follow up period |
|
|
312 |
|
|
|
313 |
#This function is given by |
|
|
314 |
#integral from 0 to t [ W ] dt' where W = time at risk for subject who had event at time t' |
|
|
315 |
#specifically |
|
|
316 |
# W = integral from 0 to t' P(had event at time t' | rec at s)P(rec at s)(t'-s) ds |
|
|
317 |
#notw the "at" in P(had event at time t') implies we need the pdf rather than the survival function |
|
|
318 |
|
|
|
319 |
internalf <- function(s,t,k,B){sapply(s,function(x,t,k,B){(k*x^(k-1)/B^k)*SurvFn@pdf(t-x)*(t-x)},t=t,k=k,B=B)} |
|
|
320 |
myf2 <- function(t,k,B){sapply(t,function(x,k,B){ |
|
|
321 |
upper <- min(x,B) |
|
|
322 |
lower <- max(x-SurvFn@followup,0) |
|
|
323 |
lagpoint <- max(x- SurvFn@LagT,0) |
|
|
324 |
if(lower > upper){ |
|
|
325 |
return(0) |
|
|
326 |
} |
|
|
327 |
|
|
|
328 |
if(lagpoint >= upper || lagpoint <= lower){ |
|
|
329 |
return( integrate(internalf,lower=lower,upper=upper,t=x,k=k,B=B)$value) |
|
|
330 |
} |
|
|
331 |
|
|
|
332 |
integrate(internalf,lower=lower,upper=lagpoint,t=x,k=k,B=B)$value+ |
|
|
333 |
integrate(internalf,lower=lagpoint,upper=upper,t=x,k=k,B=B)$value},k=k,B=B) |
|
|
334 |
|
|
|
335 |
|
|
|
336 |
} |
|
|
337 |
|
|
|
338 |
i2 <- unlist(lapply(t,function(x){ |
|
|
339 |
if(x==0) 0 else integrate(f=myf2,lower=0,upper=x,k=k,B=B)$value |
|
|
340 |
})) |
|
|
341 |
|
|
|
342 |
#Finally calculate the time on study for subjects who dropped out at the follow up period time before time t |
|
|
343 |
#The three terms multiplied are: |
|
|
344 |
#P(surviving until F) |
|
|
345 |
#Time spent at risk (i.e. F) |
|
|
346 |
#P(recruited before time t-F), |
|
|
347 |
i3 <- unlist(lapply(t,function(x){ |
|
|
348 |
if(x < SurvFn@followup) 0 else SurvFn@SurvivalFunction(SurvFn@followup)*SurvFn@followup*(min(B,x-SurvFn@followup)^k)/B^k |
|
|
349 |
})) |
|
|
350 |
|
|
|
351 |
#And sum the three values together |
|
|
352 |
i1+i2+i3 |
|
|
353 |
} |
|
|
354 |
|
|
|
355 |
# Calculate expected events using adaptive quadrature |
|
|
356 |
# |
|
|
357 |
# This function uses R's integrate function to calculate |
|
|
358 |
# the integral s^{k-1}*SurvFn(t-s) ds for given |
|
|
359 |
# k, t and SurvFn. The limits of the integral are 0 to |
|
|
360 |
# min(t,B) |
|
|
361 |
# |
|
|
362 |
# @param SurvFn sfn object. If nullf slot of sfn object is true |
|
|
363 |
# then 0 events occur (this is used |
|
|
364 |
# when calculating single arm trials) |
|
|
365 |
# @param B Accrual time. |
|
|
366 |
# @param k Non-uniformity of accrual (numeric, 1=uniform). |
|
|
367 |
# @param t A vector of prediction times for which the number |
|
|
368 |
# of events is required |
|
|
369 |
# @return A vector of number of events at the given times |
|
|
370 |
events.integ <- function( SurvFn, B, k, t ) { |
|
|
371 |
|
|
|
372 |
if(SurvFn@nullf){ |
|
|
373 |
return (rep(0,length(t))) |
|
|
374 |
} |
|
|
375 |
|
|
|
376 |
#the function to be integrated (s is the dummy variable) |
|
|
377 |
#see vignette |
|
|
378 |
myf <- function(s,k,t){s^{k-1}*SurvFn@sfn(t-s)} |
|
|
379 |
|
|
|
380 |
|
|
|
381 |
integrated_val <- unlist(lapply(t, |
|
|
382 |
function(x){ |
|
|
383 |
if(x==0){ |
|
|
384 |
return(0) #integrate unhappy if both limits are 0 and k < 1 |
|
|
385 |
} |
|
|
386 |
upperlim <- min(x,B) |
|
|
387 |
|
|
|
388 |
#If integrand is differentiable over limits then integrate in one go |
|
|
389 |
if(SurvFn@LagT == 0 || upperlim <= SurvFn@LagT ){ |
|
|
390 |
return(integrate(f=myf,lower=0,upper=upperlim,k=k,t=x)$value) |
|
|
391 |
} |
|
|
392 |
|
|
|
393 |
#when there is a lag the integrand is non-differentiable |
|
|
394 |
#so splitting the limits into two differentiable parts |
|
|
395 |
#vastly improves performance |
|
|
396 |
split.point <- upperlim - SurvFn@LagT |
|
|
397 |
return(integrate(f=myf,lower=0,upper=split.point,k=k,t=x)$value+ |
|
|
398 |
integrate(f=myf,lower=split.point,upper=upperlim,k=k,t=x)$value) |
|
|
399 |
|
|
|
400 |
} |
|
|
401 |
)) |
|
|
402 |
#See vignette for how integrated_val is used |
|
|
403 |
(pmin(t,B)/B)^k - k*integrated_val/B^k |
|
|
404 |
} |
|
|
405 |
|
|
|
406 |
|
|
|
407 |
|