Diff of /R/sfn.R [000000] .. [f2e496]

Switch to unified view

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