--- a +++ b/R/study.R @@ -0,0 +1,244 @@ +#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)) +}) +