R/fromParameterInternal.R

# The predict from parameters non-exported functions

# Calculate the expected number of event occuring at the given
# times
# 
# @param times A vector of times, see the time.pred argument
# to the S4 predict method for the Study object
# @param study A \code{Study} object
# @param sfns The survival functions the output from \code{GetSurvivalFunctions}
# @param calc.at.risk Logical, if true calculate the time at risk 
# @return A data frame containing the required data. See AnalysisResults
# slot grid for more details
CalculateEventsGivenTimes <- function(times,study,sfns,calc.at.risk=TRUE){
  #Sort out recruitment
  m         <- pmin( times, study@acc.period )
  rec.rate  <- m^study@k/study@acc.period^study@k
  Ns <- getNs(isSingleArm(study),study@r,study@N)
  recruit     <- rec.rate %*% t( Ns )
  recruit.tot <- apply( recruit, 1, sum )

  #Calculate events
  eventscdf <- lapply(sfns,function(x){events.integ(x,study@acc.period, study@k, times)})
  events <-  cbind( eventscdf[[1]]*Ns[ 1 ], eventscdf[[2]]*Ns[ 2 ] )
  events.tot <- apply( events, 1, sum )
  rounded.events.tot <- floor(events[,1])+floor(events[,2])
  
  df <- data.frame(time=times,events1=events[,1],events2=events[,2],events.tot=events.tot,recruit.tot=recruit.tot,
             rounded.events.tot,time.pred=rep(TRUE,length(times)))
  
  if(calc.at.risk){
    atriskcdf <- lapply(sfns,function(x){atrisk.integ(x,study@acc.period, study@k, times)})
    atrisk <- cbind( atriskcdf[[1]]*Ns[ 1 ], atriskcdf[[2]]*Ns[ 2 ] )
    atrisk.tot <- apply(atrisk,1,sum)
    df <- cbind(df,at.risk1=atrisk[,1],at.risk2=atrisk[,2],atrisk.tot=atrisk.tot)
  }
  
  return(df)
  
  
  
}

# Calculate the expected time a given number of events 
# will have occured
# 
# @param event.pred A vector of numbers of events, see the event.pred argument
# to the S4 predict method for the Study object
# @param study A \code{Study} object
# @param sfns The survival functions the output from \code{GetSurvivalFunctions}
# @param grid The output to \code{CalculateEventsGivenTimes} which is used
# to find good starting points for the search for thr required times 
# @param supress.warning logical, should the warning about too many events be supressed
# @return A data frame containing the required data. See AnalysisResults
# slot grid for more details
CalculateTimesGivenEvents <- function(event.pred,study,sfns,grid,supress.warning=FALSE){
  roundedEvents <- grid$rounded.events.tot
     
  startIndex <- sapply(event.pred,function(x) sum(roundedEvents < x))
  
  ans.times <- unlist(lapply(1:length(event.pred),function(x){
    
    if(startIndex[x]==nrow(grid)){
      if(!supress.warning) warning("event.pred argument too large -- too many events for study.duration")
      NA
    }
    else{
      exact_time <- subdivide(grid[startIndex[x],"time"],
                           grid[startIndex[x]+1,"time"],
                           event.pred[x],study,sfns)
    }
  }))
  
  if(all(is.na(ans.times))){
    return(data.frame())
  }
    
  retVal <- CalculateEventsGivenTimes(ans.times[!is.na(ans.times)],study,sfns)
  retVal$time.pred<-rep(FALSE,nrow(retVal))
  retVal
}


# Find the exact time at which a requested number
# of events occurs
# 
# @param low A time at which the target number of events 
# has not yet occured
# @param high A time at which the target number of events
# has already occured
# @param aim The target number of events
# @param study The study 
# @param sfns list of Survival functions
# @return The time when the requested number of events occurs
subdivide <- function(low,high,aim,study,sfns){
  while(!isTRUE(all.equal(low,high))){
    
    mid <- (low+high)/2
    ans <- CalculateEventsGivenTimes(mid,study,sfns,calc.at.risk=FALSE)$rounded.events.tot
    
    if(ans >= aim){
      high <- mid
    }
    else{
      low <- mid
    }
    
  }
  return(high)
}

# Output a list of survival functions to be in the intergration
# to calculate the number of events -- one surival function per arm
# 
# If the study has a single arm then the second entry in the list is 
# the logical 'FALSE'
# 
# @param lambda A vector of rate parameters 
# for the arms of the study
# @param lambdaot A vector of rate parameters for time < T
# for studies with a lag
# @param lag.T The amount of lag for the study (=0 if no lag)
# @param isSingleArm Logical, True is trial has a single arm
# @param shape Weibull shape parameter of the study
# @param followup The time subjects are followed from randomization (=\code{Inf} if followed
# until end of study/event occurs) - this is only used if \code{lag.T} is zero.
# @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 dropout 
# if no drop out 
# @return The survival functions
GetSurvivalFunctions <- function(lambda,lambdaot,lag.T,isSingleArm,shape,followup,dropout.shape,dropout.lambda){
  .range <- if(isSingleArm) 1 else 1:2
  ans <- lapply(.range,function(x){
    Sfn(lambda[x],lambdaot[x],lag.T,shape,followup,dropout.shape=dropout.shape,dropout.lambda=dropout.lambda[x])
  })
  
  if(isSingleArm){
    ans <- c(ans,NullSfn())
  }
  ans
}



# Calculate the required number of events to reject
# hypothesis H0: ln(HR)=0
# 
# An error is produced if more events are required than
# there are subjects on the trial. 
# See the vignette for details of the formula used.
# 
# @param r Allocation ratio 1:r, control:experiment
# @param alpha Statistical significance (assuming if a two sided 
# test then alpha has already been divided by 2)
# @param power Power of test
# @param HR Hazard ratio
# @param N total number of subjects recruited on trial
# @return The required number of events
RequiredEvents <- function(r,alpha,power,HR,N){

  events.req <- ((r + 1)*(qnorm(1 - alpha) + qnorm(power))) / 
                              (sqrt(r)*log(HR))
  
  events.req <- events.req*events.req
   
  if(events.req > N){ 
    warning(paste0("Given these settings, the (rounded) required number of events is ", round(events.req), 
                 " which is more than there are patients, please increase trial size!"))
  } 
  
  events.req  
}

# Calculate the events required for the study parameters
# the expected time for this to occur 
# and the critical HR at this number of events
#
# @param study A \code{Study} object
# @param sfns The survival functions the output from \code{GetSurvivalFunctions}
# @param grid The output to \code{CalculateEventsGivenTimes} which is used
# to find good starting points for the search for thr required times 
# @param av.HR The HR used to determine the number of events required
# @return A list containing \code{chr} the critical hazard ratio, 
# \code{critical.events.req}, the critical number of events required and
# \code{critical.data}, a data frame for the critical number of events (see grid
# slot for AnalysisResults). If single arm study then an empty data frame and 
# NA's for the other elements of the list is returned
CalculateCriticalValue <- function(study,sfns,grid,av.HR){
  
  if(isSingleArm(study)){
    chr<-as.numeric(NA)
    critical.data<-data.frame()
    events.req <- as.numeric(NA)
  }
  else{
    #Calculate the number of events required 
    alpha <-if(study@two.sided) study@alpha/2 else study@alpha
    events.req <- RequiredEvents(study@r,alpha,study@power,av.HR, study@N)
  
    ####### calculation of critical value - HR with 50% power ######
    chr <- exp(-1*((study@r + 1)*(qnorm(1 - alpha)+qnorm(0.5)))/sqrt(study@r*events.req))
    
    critical.data <- CalculateTimesGivenEvents(events.req,study,sfns,grid,supress.warning=TRUE)
  
  }
  return(list(chr=chr,critical.data=critical.data,
              critical.events.req=events.req))
  
}


# Function which calculates the average HR for a lagged study
# 
# See the vignette for details as to how this average is calculated
# 
# @param sfns A list of the survival functions
# @param study A Study object 
# @param Lagt The lag for the study
# @param LagHR The HR for time < t
# @param lambdaot The lambda for time < lag
# @param lambdatts The lambda for time > lag
# @param shape The Weibull shape parameter for the study
# @return The average HR
calculateAverageHR <- function(sfns,study,Lagt,LagHR,lambdaot,lambdatts,shape){
  
  
   
  sfnsLagt <- GetSurvivalFunctions(lambda=lambdaot,lambdaot=0,lag.T=0,
                                  isSingleArm=isSingleArm(study),
                                   shape=shape,followup=Lagt,study@dropout.shape,
                                   GetDropoutLambda(study))
   
  pot <- unlist(lapply(sfnsLagt,function(x)
         events.integ(x,study@acc.period, study@k,study@study.duration)))
  
  
  ptts <- if(Lagt < study@study.duration){  
            unlist(lapply(sfns,function(x) 
            events.integ(x,study@acc.period, study@k, study@study.duration))) - pot}
          else 
            c(0,0)
  
  
  Ns <- getNs(isSingleArm(study),study@r,study@N)
  w1 <- sum(pot*Ns)
  w2 <- sum(ptts*Ns)
  return(exp((w1*log(LagHR) + w2*log(study@HR))/(w1+w2)))
 
}


# Function that converts HR and control median into lambda
# @param median Control median
# @param hr Hazard ratio
# @param shape A Weibull Shape parameter
# @return A vector of hazard ratios
lambda.calc <- function( median, hr,shape){
  if(length(median)!=1 || length(shape)!=1 || (!is.na(hr) && length(hr) != 1)){
    stop("Invalid arguments to lambda.calc")
  }
  log(2)^(1/shape)/(median/c(1, hr^(1/shape)))
}


# Check the arguments to predict are valid
#
# @param time.pred A vector of times for which the number events are to be predicted
# @param event.pred A vector of events for which the times they are expected 
# to occur are output
# @param step.size The time between consecutive time points used for plotting the graphs
# @param study.duration the duration of the study
# @return Function throws an exception if any of the arguments are invalid
validatePredictArguments <- function(time.pred,event.pred,step.size,study.duration){

  if(!is.null(time.pred)){
    if(!is.numeric(time.pred) || any(time.pred < 0) || any(time.pred > study.duration) ){
      stop("Invalid time.pred argument")
    }
  }
  
  if(!is.null(event.pred)){
    is.wholenumber <- function(x, tol = .Machine$double.eps^0.5)  abs(x - round(x)) < tol
    if(!is.numeric(event.pred) || any(event.pred < 0) || any(!is.wholenumber(event.pred))){
      stop("Invalid event.pred argument")
    }
  
  }
  
  if(!is.numeric(step.size) || length(step.size) > 1 || step.size <= 0 || step.size > study.duration ){
    stop("Invalid step.size argument")
  }
  
}

# Return the drop out rates vector from a Study object
# @param study A Study object
# @return A vector of dropout rates - control then active for 2 arm studies, just control 
# arm for single arm studies 
GetDropoutLambda <- function(study){
  .range <- if(isSingleArm(study)) 1 else 1:2
  unlist(lapply(.range,function(x){
    lambda.calc(study@dropout[[x]]@median,1,study@dropout.shape)[1]  
  }))
}  


# Output the text concerning dropouts for the show method
# for the subject object
# 
# @param subject.text string denonting who the dropouts concern
# (e.g. "Subject" or "Control arm")
# @param dropoutspec The CtrlSpec object representing the dropout data to be output 
# @param dropoutshape If not Null then output the dropout shape
outputdropouttext <- function(subject.text,dropoutspec,dropoutshape=NULL){
  if(!is.infinite(dropoutspec@median)){
    cat(paste(subject.text,"drop out:",dropoutspec@text,"\n"))
    if(!is.null(dropoutshape)){
      if(dropoutshape==1){
        cat("Exponential drop out\n")
      }
      else{
        cat(paste("Weibull drop out with shape parameter",dropoutshape,"\n"))  
      }
    }
  }
  else{
    cat(paste(subject.text,"do not drop out","\n")) 
  }
}
scientific-computing-solutions/eventPrediction documentation built on May 29, 2019, 3:44 p.m.