R/study.R

#The Study class definition (used in predict from parameters)
#together with the exported methods. Also see study_constructors.R

#' @include lag.R common.R ctrlSpec.R sfn.R eventPrediction_package.R
NULL

##' Class defining the Study 
##' @slot HR Hazard ratio to be detected
##' @slot alpha Significance level [0,1] (see also two-sided indicator)
##' @slot power Power [0,1]
##' @slot two.sided If TRUE, two sided test will be used (i.e. alpha/2).
##' @slot r Control:Experimental subject balance (1:r), i.e. nE/nC=r. r=1 corresponds to equally 
##' many subjects in both arms. 2 means we have twice the number of subjects in the experimental arm.
##' Specifically \code{floor(r*N/(r+1))} subjects are 
##' allocated to the experimental arm and all other subjects are allocated to the control arm.
##' @slot N Number of subjects to be recruited (integer)
##' @slot study.duration Number of months the study will be going.
##' @slot ctrlSpec A CtrlSpec object which calculates the control group median. This object will be created automatically
##' when calling a constructor for the Study class.
##' @slot dropout A list of CtrlSpec object which calculates the median drop out rate for the control arm (index 1) and 
##' active arm (index 2). 
##' This object will be created automatically when calling a constructor for the study class
##' @slot dropout.shape The Weibull shape parameter of the dropout hazard function
##' @slot k non-uniformity of accrual (integer, 1=uniform). Non-uniform accrual is allowed for 
##' using the following distribution for the probability of a patient entering the trial at time \eqn{b} 
##' within the accrual period \eqn{[0,B]}: \eqn{F(b)=b_k/B_k}; \eqn{f(b)=k b_{k-1}/B_k} where \eqn{k} is the 
##' measure of non-uniformity (\eqn{k>0}). \eqn{k=1} indicates uniform accrual. This implies that during 
##' the first half of the accrual period, \eqn{1/2^k} of the patients will be recruited. Half of the patients 
##' will be recruited by time \eqn{B/2^{1/k}}. 
##' @slot acc.period Accrual time in months
##' @slot shape The Weibull shape parameter
##' @slot followup The time a subject is followed after randomization, if Inf then there is no fixed time period
##' @slot type Character: The study type, either "Oncology" or "CRGI"
##' @slot lag.settings The \code{LaggedEffect} object describing any lag effect for the study 
##' @export 
setClass( "Study", 
          slots= list( HR = "numeric",  # Hazard ratio
                       alpha = "numeric",
                       power = "numeric",
                       two.sided = "logical",
                       r = "numeric",  
                       N = "numeric",  # Patients to be recruited
                       study.duration = "numeric",  # Length of study (In months)
                       ctrlSpec = "CtrlSpec",  # Median of control arm
                       dropout = "list",
                       dropout.shape ="numeric",
                       k = "numeric",  
                       acc.period = "numeric",
                       shape="numeric",
                       followup="numeric",
                       type="character",
                       lag.settings = "LagEffect"),
          
          validity = function(object){
            is.wholenumber <- function(x, tol = .Machine$double.eps^0.5)  abs(x - round(x)) < tol          
            ans <- ""
            if(object@shape <= 0) ans <- paste(ans,"Invalid shape.")
            if(object@acc.period <= 0) ans <- paste(ans,"Invalid acc.period")
            if(object@k <= 0) ans <- paste(ans,"Invalid k.")
            if(object@N <= 0 || !is.wholenumber(object@N) || length(object@N)>1 ) ans <- paste(ans,"Invalid N.")
            if(object@study.duration <= 0 ) ans <- paste(ans,"Invalid study.duration.")
            if(object@r < 0 ) ans <- paste(ans,"Invalid r.")
                        
            if(object@study.duration <= object@acc.period) 
              ans <- paste(ans,"acc.period must be < study.duration")
            
            if(object@r == 0){
              if(!is.na(object@HR)) ans <- paste(ans,"HR must be NA if r = 0")
              if(!is.na(object@power)) ans <- paste(ans,"power must be NA if r = 0")
              if(!is.na(object@alpha)) ans <- paste(ans,"alpha must be NA if r = 0")
            }
            else{
              if(is.na(object@HR) || object@HR >= 1 || object@HR <= 0)  ans <- paste(ans,"Invalid HR.") 
              if(object@alpha >= 1 || object@alpha <= 0 ) ans <- paste(ans,"Invalid alpha.")
              if(object@power >= 1 || object@power <= 0 ) ans <- paste(ans,"Invalid power.")
            }
            
            if(!object@type %in% c("Oncology","CRGI")) ans <- paste(ans,"Invalid type.") 
            
            if(length(object@followup) > 1 || object@followup <= 0 ) ans <- paste(ans,"Invalid followup.")
            
            if(class(object@lag.settings)!="LagEffect")ans <- paste(ans,"Invalid lag.settings")
            
            if(!isNullLag(object@lag.settings)){
              if(isSingleArm(object) && !is.na(object@lag.settings@L.HazardRatio)){
                ans <- paste(ans,"lag.settings@L.HazardRatio must be as.numeric(NA) if study has one arm")
              }
              if(!isSingleArm(object) && is.na(object@lag.settings@L.HazardRatio)){
                ans <- paste(ans,"lag.settings@L.HazardRatio cannot be NA if study has more than one arm")
              }
            }
                        
            if(!is.infinite(object@followup) && !isNullLag(object@lag.settings)){
              ans <- paste(ans, "Cannot use lagged settings with a study which has a finite follow up time")
            }
            
            if(ans=="") return(TRUE)
            ans
          }
          
)




##' @name show
##' @rdname show-methods
##' @aliases show,Study-method
##' @export
setMethod("show", signature(object="Study"), 
    function(object) {
      cat("Study definition:\n")
      cat(paste("Number of Patients (N):",object@N,"\n"))
      cat(paste("Study duration:",object@study.duration,"months\n"))
      cat(paste("Accrual period:",object@acc.period,"months\n"))
      cat(paste("Accrual uniformity (k):",object@k,"\n"))
      if(!isSingleArm(object)){
        cat(paste("Control arm survival:"),object@ctrlSpec@text,"\n")
        cat(paste("Hazard Ratio:",object@HR,"\n"))
        cat(paste("Ratio of control to experimental 1:",object@r,"\n",sep=""))
        cat(paste("alpha:",object@alpha))
        if(object@two.sided){
          cat("(two sided)\n")
        }
        else{
          cat("(one sided)\n")
        }
        cat(paste("Power:",object@power,"\n"))
      }
    else{
      cat(paste("Survival:"),object@ctrlSpec@text,"\n")
      cat("Single Arm trial\n")
    } 
      
    if(object@shape==1){
      cat("Exponential survival\n")
    }
    else{
      cat(paste("Weibull survival with shape parameter",object@shape,"\n"))    
    }
      
    if(!is.infinite(object@followup)){
      cat("Subject follow up period:",object@followup,"months\n")
    } 
      
    if(isSingleArm(object)){
      outputdropouttext("Subject",object@dropout[[1]],object@dropout.shape)
    }  
    else{
      outputdropouttext("Control Arm",object@dropout[[1]])
      outputdropouttext("Active Arm",object@dropout[[2]],object@dropout.shape)
    }
    show(object@lag.settings) 
})



##' Is the \code{Study} a single arm study
##' @rdname isSingleArm-methods
##' @name isSingleArm
##' @param study A \code{Study} object
##' @return TRUE if study is single arm, FALSE otherwise
##' @export
setGeneric("isSingleArm",
           def=function(study)
           standardGeneric("isSingleArm"))




##' @rdname isSingleArm-methods
##' @aliases isSingleArm,Study-method
##' @name isSingleArm
##' @export
setMethod("isSingleArm",representation(study="Study"),
          function(study){
            study@r==0
          })


##' @name predict
##' @rdname predict-methods
##' @aliases predict,Study-method
##' @param step.size The resolution of the grid to be used to calculate the time v 
##' expected events curves 
##' @export
setMethod( "predict", representation( object="Study" ), 
  function( object, time.pred=NULL,
            event.pred=NULL,step.size=0.5 ) {
    
    study <- object
    lagged <- study@lag.settings
    validatePredictArguments(time.pred,event.pred,step.size,study@study.duration)
        
    #times for the plotting function
    grid.times <- seq( 0, study@study.duration, step.size )
    
    #Next calculate the values of rate parameters
    lambda    <- lambda.calc( study@ctrlSpec@median, study@HR, study@shape )
    if(isNullLag(lagged)){
      lambdaot <- as.numeric(NA)
    }
    else{
      lambdaot  <- lambda.calc( lagged@ctrlSpec@median,  lagged@L.HazardRatio,study@shape )  
    }
    
    dropout.lambda <- GetDropoutLambda(study)
        
    #Create the survival functions
    sfns <- GetSurvivalFunctions(lambda,lambdaot,lagged@Lag.T,isSingleArm(study),study@shape,
                                 study@followup,study@dropout.shape,dropout.lambda)
       
    #Calculate event details for plot 
    grid <- CalculateEventsGivenTimes(grid.times,study,sfns,calc.at.risk=FALSE)  
    
    #Calculate average HR
    av.HR <-if(isNullLag(lagged) || isSingleArm(study)) study@HR 
            else calculateAverageHR(sfns,study,lagged@Lag.T,lagged@L.HazardRatio,
                                    lambdaot,lambda,study@shape)
   
    #Calculate the critical value
    critical.data <- CalculateCriticalValue(study,sfns,grid,av.HR)
    
    #Calculate events at given times
    predict.data <- if(!is.null(time.pred)) CalculateEventsGivenTimes(time.pred,study,sfns) 
                    else predict.data <- data.frame()
    
    
    #Calculate times for given number of events
    if(!is.null(event.pred)){
      predict.data <- rbind(predict.data,CalculateTimesGivenEvents(event.pred,study,sfns,grid))
    }
    
    return(AnalysisResults( 
           critical.HR = critical.data[["chr"]], 
           critical.data = critical.data[["critical.data"]], 
           critical.events.req= critical.data[["critical.events.req"]],
           av.hr=av.HR,
           grid=grid,
           predict.data=predict.data,
           study=study,
           sfns=sfns))
})
scientific-computing-solutions/eventPrediction documentation built on May 29, 2019, 3:44 p.m.