--- a +++ b/R/sfn.R @@ -0,0 +1,407 @@ +#Code for the predict from parameters Survival functions/ +#functions to be used in the integral when calculating the +#expected event times and times at risk + + +##' A Class containing the Survival Function (for a single arm) +##' used in the integral to calculate event times +##' in predict from parameters. It should not be created by the user +##' but is created for the user (see sfns slot of \code{AnalysisResult}) +##' +##' @slot sfn A function sfn(x) is the function to be included in the events.integrate +##' procedure when using predict from parameters. +##' Specifically sfn(x) = 1 - P(had event [i.e. not dropout] by time x) and when no drop outs +##' this is exactly the survival function. +##' If using dropouts this is not quite the survival function +##' However, the output of LatexSurvivalFn(object) is the survival function. +##' @slot SurvivalFunction The actual survival function (if drop outs/finite followup are used then this will +##' not equal sfn). If finite follow up is used then S(x) = 0 for x > followup +##' @slot pdf The pdf function associated with the survival function. If finite follow up is used +##' then pdf(x) should = 0 for x > followup +##' @slot nullf Logical, TRUE if the object represents NULL (i.e. a survival function for +##' a second arm in a single arm study) +##' @slot lambda The rate parameters for the arm of trial. +##' In a trial with lag, this is the rate parameters for time > T +##' @slot lambdaot If a lag was used then the rate parameters +##' for time < T otherwise NA +##' @slot shape The Weibull shape parameter +##' @slot LagT The lagtime for the survival function (0 for no lag) +##' @slot followup The follow up time for each subject +##' (Inf for studies with no fixed followup) +##' @slot dropout.shape The Weibull shape parameter for the drop out hazard function +##' @slot dropout.lambda The rate parameter for the drop out hazard function = 0 if no dropout +##' +##' @export +setClass("Sfn", + slots=list(sfn="function", + SurvivalFunction="function", + pdf="function", + nullf="logical", + lambda="numeric", + lambdaot="numeric", + shape="numeric", + LagT="numeric", + followup="numeric", + dropout.shape="numeric", + dropout.lambda="numeric" + ) +) + +# Constructor for \code{Sfn} object +# @param lambda The rate parameters for the arm of the trial. +# In a trial with lag, this is the rate parameters for time > T +# @param lambdaot If a lag was used then the rate parameters +# for time < T otherwise NA +# @param lag.T The lagtime for the survival function (0 for no lag) +# @param shape The Weibull shape parameter +# @param followup The follow up time for each subject (Inf for studies with no fixed followup) +# @param dropout.shape The Weibull shape parameter for the drop out hazard function +# @param dropout.lambda The rate parameter for the drop out hazard function - 0 +# if no drop out +Sfn <- function(lambda,lambdaot,lag.T,shape,followup,dropout.shape=1,dropout.lambda=0){ + + if(lag.T==0){ + lambdaot <- as.numeric(NA) + } + + #survival function without dropouts or fixed follow up... + f <- if(lag.T==0) + function(x){ + return(exp(-(lambda*x)^shape)) + } + else + function(x){ + ifelse(x<lag.T, exp(-(lambdaot*x)^shape),exp(-(lambda*x)^shape+(lambda^shape-lambdaot^shape)*lag.T^shape)) + } + + #... and its corresponding hazard function + h <- if(lag.T==0) + function(x){ + return(shape*lambda^shape*x^(shape-1)) + } + else + function(x){ + ifelse(x<lag.T,shape*lambdaot^shape*x^(shape-1),shape*lambda^shape*x^(shape-1)) + } + + ################################################################ + + #next we calulate the function giving 1- P(having event) which is placed in the sfn slot + #of the object when including dropouts this function is the integrated or cumulative hazard for hazard + #event. + + #The integrand of the cumulative hazard function + f2 <- function(x){h(x)*f(x)*exp(-(x*dropout.lambda)^dropout.shape)} + + #we precompute the value of the integral when there is dropout and lag + lag.T.int <- if(lag.T != 0 && dropout.lambda != 0) integrate(f=f2,lower=0,upper=lag.T)$value else 0 + + #we precompute the value of 1-P(having event by time=follow up) to save calculating the integral + #multiple times + follow.up.int <- if(dropout.lambda!=0 && !is.infinite(followup)) + integrate(f=f2,lower=0,upper=followup)$value else 0 + + + #include drop out and follow up if required for slot sfn (so no longer the survival function) + dropf <- function(x){ + #First deal with case of no dropout + if(dropout.lambda==0){ + return(ifelse(x<followup,f(x),f(followup))) + } + + #next no lag but followup + if(lag.T==0 && !is.infinite(followup)){ + return(sapply(x, function(t){ + ifelse(t < followup, + 1-integrate(f=f2,lower=0,upper=t)$value, + 1-follow.up.int) #1-P(have event by time t > followup) = 1-P(have event by time followup) + })) + } + + #Next no lag or followup < lag (so no discontinuity) + if(lag.T==0 || followup <= lag.T){ + return(sapply(x,function(t){1-integrate(f=f2,lower=0,upper=min(t,followup))$value})) + } + + #finally case with lag and follow up > lag + sapply(x, function(t){ + ifelse(t < lag.T, + 1-integrate(f=f2,lower=0,upper=min(t,followup))$value, + 1-integrate(f=f2,lower=lag.T,upper=min(t,followup))$value-lag.T.int #split integral into two parts + ) + }) + + } + #the function to be included in the events integral + sfn <- dropf + + ######################################## + + #Now we deal with the Survival function + #Add drop outs + S <- function(x){ + if(dropout.lambda==0){ + return(f(x)) + } + f(x)*exp(-(x*dropout.lambda)^dropout.shape) + } + + #and fixed follow ups + #In '<=' the equal sign here is crucial as we need + #to use S(followup) when calculating the atrisk information + SurvivalFunction <- function(x){ifelse(x<=followup,S(x),0)} + + ####################################### + + ##Next work on pdf + + dropouth <- function(x){ + if(dropout.lambda==0){ + return(0) + } + return((dropout.lambda*dropout.shape)*(dropout.lambda*x)^(dropout.shape-1)) + } + + #pdf is hazard function * survival function + pdfwithdropout <- if(dropout.lambda==0) function(x){S(x)*h(x)} else function(x){S(x)*(h(x)+dropouth(x))} + + ##and fixed follow up + pdf <- if(is.infinite(followup)) pdfwithdropout else function(x){ifelse(x<=followup,pdfwithdropout(x),0)} + + new("Sfn",sfn=sfn,nullf=FALSE, + lambda=lambda,lambdaot=lambdaot,LagT=lag.T,shape=shape,followup=followup, + dropout.lambda=dropout.lambda,dropout.shape=dropout.shape,SurvivalFunction=SurvivalFunction,pdf=pdf) + +} + +# Create a Null version of the \code{Sfn} object +# For use as the survival function for the experimental arm +# of a single arm trial +# @return A \code{Sfn} object +NullSfn <- function(){ + f <- function(x){0} + new("Sfn",sfn=f,nullf=TRUE,lambda=0,lambdaot=0,LagT=0,shape=0,followup=0, + dropout.shape=1,dropout.lambda=0,SurvivalFunction=f,pdf=f) +} + +##' Method to output a Latex String of the object +##' +##' @param results Object to output +##' @param \ldots Additional parameters to be passed into the function +##' @param lambda The symbol to be used for the arm's rate +##' parameter, if lagged study then this is a vector of before and after lag +##' symbols. The latex backslash character needs to be escaped +##' @param shape The symbol to be used for Weibull shape parameter +##' @return A latex string of the object (for \code{Sfn} this is the Survival function) +##' @rdname LatexSurvivalFn-methods +##' @name LatexSurvivalFn +##' @export +setGeneric( "LatexSurvivalFn", + def = function( results, ... ) + standardGeneric( "LatexSurvivalFn" )) + + +##' @name LatexSurvivalFn +##' @rdname LatexSurvivalFn-methods +##' @aliases LatexSurvivalFn,Sfn-method +##' @export +setMethod("LatexSurvivalFn", + signature("Sfn"), + function(results,decimalplaces,lambda,shape){ + + #First set the function which will produce the output + if(results@LagT!= 0 && results@shape ==1 ){ + LatexOutput <- function(lambda,shape,followup,dropouttext){ + return(paste("$$ S(t) = \\begin{cases} \\exp(-",lambda[1],"t",dropouttext,") & \\quad \\text{if } t \\le T \\\\ + \\exp(-",lambda[2],"t-(",lambda[1],"-",lambda[2],")T",dropouttext,") & \\quad \\text{if } t > T\\ \\end{cases} $$")) + } + }else if(results@LagT!= 0 && results@shape !=1){ + LatexOutput <- function(lambda,shape,followup,dropouttext){ + return(paste("$$ S(t) = \\begin{cases} \\exp(-(",lambda[1],"t)^",shape,dropouttext,") & \\quad \\text{if } t \\le T \\\\ + \\exp(-(",lambda[2],"t)^",shape,"-(",lambda[1],"^",shape, + "-",lambda[2],"^",shape,")T^",shape,dropouttext,") & \\quad \\text{if } t > T\\ \\end{cases} $$")) + } + }else if(results@LagT == 0 && results@shape ==1){ + LatexOutput <- function(lambda,shape,followup,dropouttext){ + fuptxt <- if(is.infinite(followup)) "" else paste("\\quad (\\text{if } t <",round(followup,decimalplaces),")") + return(paste("$$S(t) = \\exp(-",lambda[1],"t",dropouttext,")",fuptxt,"$$")) + } + }else{ + LatexOutput <- function(lambda,shape,followup,dropouttext){ + fuptxt <- if(is.infinite(followup)) "" else paste("\\quad (\\text{if } t <",round(followup,decimalplaces),")") + return(paste("$$S(t) = \\exp(-(",lambda[1],"t)^",shape,dropouttext,")",fuptxt,"$$")) + } + } + + #Then produce the output + ans <- "" + extra <- "" + + dropouttext <- "" + if(results@dropout.lambda!=0){ + + if(results@dropout.shape==1){ + dropouttext <- paste("-",lambda[3],"t") + } + else{ + dropouttext <- paste("-(",lambda[3],"t)^",shape[2]) + } + } + + + if(results@LagT!=0){ + extra <- paste(",\\:",lambda[2],"=",round(results@lambda,decimalplaces)) + lambda_val <- round(results@lambdaot,decimalplaces) + } + else{ + lambda_val <- round(results@lambda,decimalplaces) + } + + ans <- paste(ans,LatexOutput(lambda,shape=shape[1],followup=results@followup,dropouttext),"\n") + ans <- paste(ans,"$$",lambda[1],"=",lambda_val,extra) + + if(results@shape !=1) { + ans <- paste(ans,",\\:",shape[1],"=",round(results@shape,decimalplaces)) + } + + if(results@dropout.lambda!=0){ + ans <- paste(ans,",\\:",lambda[3],"=",round(results@dropout.lambda,decimalplaces)) + + if(results@dropout.shape!=1){ + ans <- paste(ans,",\\:",shape[2],"=",round(results@dropout.shape,decimalplaces)) + } + } + + ans <- paste(ans,"$$\n") + return(ans) + } +) + +# Calculate the expected time at risk +# +# @param SurvFn sfn object. If nullf slot of sfn object is true +# then 0 time at risk occurs occur (this is used +# when calculating single arm trials) +# @param B Accrual time. +# @param k Non-uniformity of accrual (numeric, 1=uniform). +# @param t A vector of prediction times for which the expected time +# at risk is required +# @return A vector of time at risk at the given times +atrisk.integ <- function( SurvFn, B, k, t ) { + if(SurvFn@nullf){ + return (rep(0,length(t))) + } + + #see the predict from parameters vignette for the three formula calculated here + #and their derivations + + ##################### + + #First calculate the amount of time of subjects who are still on the trial at time t + #= 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 + #and time_at_risk=t-s + myf1 <- function(s,k,t){(k*s^(k-1)/B^k)*SurvFn@SurvivalFunction(t-s)*(t-s)} + + i1 <- unlist(lapply(t, + function(x){ + if(x==0) 0 else integrate(f=myf1,lower=0,upper=min(x,B),k=k,t=x)$value + })) + + #Next calculate the amount of time of subjects who had an event or dropped out before time t + #This does not include subjects who have dropped out as they were censored at the end of the follow up period + + #This function is given by + #integral from 0 to t [ W ] dt' where W = time at risk for subject who had event at time t' + #specifically + # W = integral from 0 to t' P(had event at time t' | rec at s)P(rec at s)(t'-s) ds + #notw the "at" in P(had event at time t') implies we need the pdf rather than the survival function + + 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)} + myf2 <- function(t,k,B){sapply(t,function(x,k,B){ + upper <- min(x,B) + lower <- max(x-SurvFn@followup,0) + lagpoint <- max(x- SurvFn@LagT,0) + if(lower > upper){ + return(0) + } + + if(lagpoint >= upper || lagpoint <= lower){ + return( integrate(internalf,lower=lower,upper=upper,t=x,k=k,B=B)$value) + } + + integrate(internalf,lower=lower,upper=lagpoint,t=x,k=k,B=B)$value+ + integrate(internalf,lower=lagpoint,upper=upper,t=x,k=k,B=B)$value},k=k,B=B) + + + } + + i2 <- unlist(lapply(t,function(x){ + if(x==0) 0 else integrate(f=myf2,lower=0,upper=x,k=k,B=B)$value + })) + + #Finally calculate the time on study for subjects who dropped out at the follow up period time before time t + #The three terms multiplied are: + #P(surviving until F) + #Time spent at risk (i.e. F) + #P(recruited before time t-F), + i3 <- unlist(lapply(t,function(x){ + if(x < SurvFn@followup) 0 else SurvFn@SurvivalFunction(SurvFn@followup)*SurvFn@followup*(min(B,x-SurvFn@followup)^k)/B^k + })) + + #And sum the three values together + i1+i2+i3 +} + +# Calculate expected events using adaptive quadrature +# +# This function uses R's integrate function to calculate +# the integral s^{k-1}*SurvFn(t-s) ds for given +# k, t and SurvFn. The limits of the integral are 0 to +# min(t,B) +# +# @param SurvFn sfn object. If nullf slot of sfn object is true +# then 0 events occur (this is used +# when calculating single arm trials) +# @param B Accrual time. +# @param k Non-uniformity of accrual (numeric, 1=uniform). +# @param t A vector of prediction times for which the number +# of events is required +# @return A vector of number of events at the given times +events.integ <- function( SurvFn, B, k, t ) { + + if(SurvFn@nullf){ + return (rep(0,length(t))) + } + + #the function to be integrated (s is the dummy variable) + #see vignette + myf <- function(s,k,t){s^{k-1}*SurvFn@sfn(t-s)} + + + integrated_val <- unlist(lapply(t, + function(x){ + if(x==0){ + return(0) #integrate unhappy if both limits are 0 and k < 1 + } + upperlim <- min(x,B) + + #If integrand is differentiable over limits then integrate in one go + if(SurvFn@LagT == 0 || upperlim <= SurvFn@LagT ){ + return(integrate(f=myf,lower=0,upper=upperlim,k=k,t=x)$value) + } + + #when there is a lag the integrand is non-differentiable + #so splitting the limits into two differentiable parts + #vastly improves performance + split.point <- upperlim - SurvFn@LagT + return(integrate(f=myf,lower=0,upper=split.point,k=k,t=x)$value+ + integrate(f=myf,lower=split.point,upper=upperlim,k=k,t=x)$value) + + } + )) + #See vignette for how integrated_val is used + (pmin(t,B)/B)^k - k*integrated_val/B^k +} + + + \ No newline at end of file