|
a |
|
b/R/study.R |
|
|
1 |
#The Study class definition (used in predict from parameters) |
|
|
2 |
#together with the exported methods. Also see study_constructors.R |
|
|
3 |
|
|
|
4 |
#' @include lag.R common.R ctrlSpec.R sfn.R eventPrediction_package.R |
|
|
5 |
NULL |
|
|
6 |
|
|
|
7 |
##' Class defining the Study |
|
|
8 |
##' @slot HR Hazard ratio to be detected |
|
|
9 |
##' @slot alpha Significance level [0,1] (see also two-sided indicator) |
|
|
10 |
##' @slot power Power [0,1] |
|
|
11 |
##' @slot two.sided If TRUE, two sided test will be used (i.e. alpha/2). |
|
|
12 |
##' @slot r Control:Experimental subject balance (1:r), i.e. nE/nC=r. r=1 corresponds to equally |
|
|
13 |
##' many subjects in both arms. 2 means we have twice the number of subjects in the experimental arm. |
|
|
14 |
##' Specifically \code{floor(r*N/(r+1))} subjects are |
|
|
15 |
##' allocated to the experimental arm and all other subjects are allocated to the control arm. |
|
|
16 |
##' @slot N Number of subjects to be recruited (integer) |
|
|
17 |
##' @slot study.duration Number of months the study will be going. |
|
|
18 |
##' @slot ctrlSpec A CtrlSpec object which calculates the control group median. This object will be created automatically |
|
|
19 |
##' when calling a constructor for the Study class. |
|
|
20 |
##' @slot dropout A list of CtrlSpec object which calculates the median drop out rate for the control arm (index 1) and |
|
|
21 |
##' active arm (index 2). |
|
|
22 |
##' This object will be created automatically when calling a constructor for the study class |
|
|
23 |
##' @slot dropout.shape The Weibull shape parameter of the dropout hazard function |
|
|
24 |
##' @slot k non-uniformity of accrual (integer, 1=uniform). Non-uniform accrual is allowed for |
|
|
25 |
##' using the following distribution for the probability of a patient entering the trial at time \eqn{b} |
|
|
26 |
##' 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 |
|
|
27 |
##' measure of non-uniformity (\eqn{k>0}). \eqn{k=1} indicates uniform accrual. This implies that during |
|
|
28 |
##' the first half of the accrual period, \eqn{1/2^k} of the patients will be recruited. Half of the patients |
|
|
29 |
##' will be recruited by time \eqn{B/2^{1/k}}. |
|
|
30 |
##' @slot acc.period Accrual time in months |
|
|
31 |
##' @slot shape The Weibull shape parameter |
|
|
32 |
##' @slot followup The time a subject is followed after randomization, if Inf then there is no fixed time period |
|
|
33 |
##' @slot type Character: The study type, either "Oncology" or "CRGI" |
|
|
34 |
##' @slot lag.settings The \code{LaggedEffect} object describing any lag effect for the study |
|
|
35 |
##' @export |
|
|
36 |
setClass( "Study", |
|
|
37 |
slots= list( HR = "numeric", # Hazard ratio |
|
|
38 |
alpha = "numeric", |
|
|
39 |
power = "numeric", |
|
|
40 |
two.sided = "logical", |
|
|
41 |
r = "numeric", |
|
|
42 |
N = "numeric", # Patients to be recruited |
|
|
43 |
study.duration = "numeric", # Length of study (In months) |
|
|
44 |
ctrlSpec = "CtrlSpec", # Median of control arm |
|
|
45 |
dropout = "list", |
|
|
46 |
dropout.shape ="numeric", |
|
|
47 |
k = "numeric", |
|
|
48 |
acc.period = "numeric", |
|
|
49 |
shape="numeric", |
|
|
50 |
followup="numeric", |
|
|
51 |
type="character", |
|
|
52 |
lag.settings = "LagEffect"), |
|
|
53 |
|
|
|
54 |
validity = function(object){ |
|
|
55 |
is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) abs(x - round(x)) < tol |
|
|
56 |
ans <- "" |
|
|
57 |
if(object@shape <= 0) ans <- paste(ans,"Invalid shape.") |
|
|
58 |
if(object@acc.period <= 0) ans <- paste(ans,"Invalid acc.period") |
|
|
59 |
if(object@k <= 0) ans <- paste(ans,"Invalid k.") |
|
|
60 |
if(object@N <= 0 || !is.wholenumber(object@N) || length(object@N)>1 ) ans <- paste(ans,"Invalid N.") |
|
|
61 |
if(object@study.duration <= 0 ) ans <- paste(ans,"Invalid study.duration.") |
|
|
62 |
if(object@r < 0 ) ans <- paste(ans,"Invalid r.") |
|
|
63 |
|
|
|
64 |
if(object@study.duration <= object@acc.period) |
|
|
65 |
ans <- paste(ans,"acc.period must be < study.duration") |
|
|
66 |
|
|
|
67 |
if(object@r == 0){ |
|
|
68 |
if(!is.na(object@HR)) ans <- paste(ans,"HR must be NA if r = 0") |
|
|
69 |
if(!is.na(object@power)) ans <- paste(ans,"power must be NA if r = 0") |
|
|
70 |
if(!is.na(object@alpha)) ans <- paste(ans,"alpha must be NA if r = 0") |
|
|
71 |
} |
|
|
72 |
else{ |
|
|
73 |
if(is.na(object@HR) || object@HR >= 1 || object@HR <= 0) ans <- paste(ans,"Invalid HR.") |
|
|
74 |
if(object@alpha >= 1 || object@alpha <= 0 ) ans <- paste(ans,"Invalid alpha.") |
|
|
75 |
if(object@power >= 1 || object@power <= 0 ) ans <- paste(ans,"Invalid power.") |
|
|
76 |
} |
|
|
77 |
|
|
|
78 |
if(!object@type %in% c("Oncology","CRGI")) ans <- paste(ans,"Invalid type.") |
|
|
79 |
|
|
|
80 |
if(length(object@followup) > 1 || object@followup <= 0 ) ans <- paste(ans,"Invalid followup.") |
|
|
81 |
|
|
|
82 |
if(class(object@lag.settings)!="LagEffect")ans <- paste(ans,"Invalid lag.settings") |
|
|
83 |
|
|
|
84 |
if(!isNullLag(object@lag.settings)){ |
|
|
85 |
if(isSingleArm(object) && !is.na(object@lag.settings@L.HazardRatio)){ |
|
|
86 |
ans <- paste(ans,"lag.settings@L.HazardRatio must be as.numeric(NA) if study has one arm") |
|
|
87 |
} |
|
|
88 |
if(!isSingleArm(object) && is.na(object@lag.settings@L.HazardRatio)){ |
|
|
89 |
ans <- paste(ans,"lag.settings@L.HazardRatio cannot be NA if study has more than one arm") |
|
|
90 |
} |
|
|
91 |
} |
|
|
92 |
|
|
|
93 |
if(!is.infinite(object@followup) && !isNullLag(object@lag.settings)){ |
|
|
94 |
ans <- paste(ans, "Cannot use lagged settings with a study which has a finite follow up time") |
|
|
95 |
} |
|
|
96 |
|
|
|
97 |
if(ans=="") return(TRUE) |
|
|
98 |
ans |
|
|
99 |
} |
|
|
100 |
|
|
|
101 |
) |
|
|
102 |
|
|
|
103 |
|
|
|
104 |
|
|
|
105 |
|
|
|
106 |
##' @name show |
|
|
107 |
##' @rdname show-methods |
|
|
108 |
##' @aliases show,Study-method |
|
|
109 |
##' @export |
|
|
110 |
setMethod("show", signature(object="Study"), |
|
|
111 |
function(object) { |
|
|
112 |
cat("Study definition:\n") |
|
|
113 |
cat(paste("Number of Patients (N):",object@N,"\n")) |
|
|
114 |
cat(paste("Study duration:",object@study.duration,"months\n")) |
|
|
115 |
cat(paste("Accrual period:",object@acc.period,"months\n")) |
|
|
116 |
cat(paste("Accrual uniformity (k):",object@k,"\n")) |
|
|
117 |
if(!isSingleArm(object)){ |
|
|
118 |
cat(paste("Control arm survival:"),object@ctrlSpec@text,"\n") |
|
|
119 |
cat(paste("Hazard Ratio:",object@HR,"\n")) |
|
|
120 |
cat(paste("Ratio of control to experimental 1:",object@r,"\n",sep="")) |
|
|
121 |
cat(paste("alpha:",object@alpha)) |
|
|
122 |
if(object@two.sided){ |
|
|
123 |
cat("(two sided)\n") |
|
|
124 |
} |
|
|
125 |
else{ |
|
|
126 |
cat("(one sided)\n") |
|
|
127 |
} |
|
|
128 |
cat(paste("Power:",object@power,"\n")) |
|
|
129 |
} |
|
|
130 |
else{ |
|
|
131 |
cat(paste("Survival:"),object@ctrlSpec@text,"\n") |
|
|
132 |
cat("Single Arm trial\n") |
|
|
133 |
} |
|
|
134 |
|
|
|
135 |
if(object@shape==1){ |
|
|
136 |
cat("Exponential survival\n") |
|
|
137 |
} |
|
|
138 |
else{ |
|
|
139 |
cat(paste("Weibull survival with shape parameter",object@shape,"\n")) |
|
|
140 |
} |
|
|
141 |
|
|
|
142 |
if(!is.infinite(object@followup)){ |
|
|
143 |
cat("Subject follow up period:",object@followup,"months\n") |
|
|
144 |
} |
|
|
145 |
|
|
|
146 |
if(isSingleArm(object)){ |
|
|
147 |
outputdropouttext("Subject",object@dropout[[1]],object@dropout.shape) |
|
|
148 |
} |
|
|
149 |
else{ |
|
|
150 |
outputdropouttext("Control Arm",object@dropout[[1]]) |
|
|
151 |
outputdropouttext("Active Arm",object@dropout[[2]],object@dropout.shape) |
|
|
152 |
} |
|
|
153 |
show(object@lag.settings) |
|
|
154 |
}) |
|
|
155 |
|
|
|
156 |
|
|
|
157 |
|
|
|
158 |
##' Is the \code{Study} a single arm study |
|
|
159 |
##' @rdname isSingleArm-methods |
|
|
160 |
##' @name isSingleArm |
|
|
161 |
##' @param study A \code{Study} object |
|
|
162 |
##' @return TRUE if study is single arm, FALSE otherwise |
|
|
163 |
##' @export |
|
|
164 |
setGeneric("isSingleArm", |
|
|
165 |
def=function(study) |
|
|
166 |
standardGeneric("isSingleArm")) |
|
|
167 |
|
|
|
168 |
|
|
|
169 |
|
|
|
170 |
|
|
|
171 |
##' @rdname isSingleArm-methods |
|
|
172 |
##' @aliases isSingleArm,Study-method |
|
|
173 |
##' @name isSingleArm |
|
|
174 |
##' @export |
|
|
175 |
setMethod("isSingleArm",representation(study="Study"), |
|
|
176 |
function(study){ |
|
|
177 |
study@r==0 |
|
|
178 |
}) |
|
|
179 |
|
|
|
180 |
|
|
|
181 |
##' @name predict |
|
|
182 |
##' @rdname predict-methods |
|
|
183 |
##' @aliases predict,Study-method |
|
|
184 |
##' @param step.size The resolution of the grid to be used to calculate the time v |
|
|
185 |
##' expected events curves |
|
|
186 |
##' @export |
|
|
187 |
setMethod( "predict", representation( object="Study" ), |
|
|
188 |
function( object, time.pred=NULL, |
|
|
189 |
event.pred=NULL,step.size=0.5 ) { |
|
|
190 |
|
|
|
191 |
study <- object |
|
|
192 |
lagged <- study@lag.settings |
|
|
193 |
validatePredictArguments(time.pred,event.pred,step.size,study@study.duration) |
|
|
194 |
|
|
|
195 |
#times for the plotting function |
|
|
196 |
grid.times <- seq( 0, study@study.duration, step.size ) |
|
|
197 |
|
|
|
198 |
#Next calculate the values of rate parameters |
|
|
199 |
lambda <- lambda.calc( study@ctrlSpec@median, study@HR, study@shape ) |
|
|
200 |
if(isNullLag(lagged)){ |
|
|
201 |
lambdaot <- as.numeric(NA) |
|
|
202 |
} |
|
|
203 |
else{ |
|
|
204 |
lambdaot <- lambda.calc( lagged@ctrlSpec@median, lagged@L.HazardRatio,study@shape ) |
|
|
205 |
} |
|
|
206 |
|
|
|
207 |
dropout.lambda <- GetDropoutLambda(study) |
|
|
208 |
|
|
|
209 |
#Create the survival functions |
|
|
210 |
sfns <- GetSurvivalFunctions(lambda,lambdaot,lagged@Lag.T,isSingleArm(study),study@shape, |
|
|
211 |
study@followup,study@dropout.shape,dropout.lambda) |
|
|
212 |
|
|
|
213 |
#Calculate event details for plot |
|
|
214 |
grid <- CalculateEventsGivenTimes(grid.times,study,sfns,calc.at.risk=FALSE) |
|
|
215 |
|
|
|
216 |
#Calculate average HR |
|
|
217 |
av.HR <-if(isNullLag(lagged) || isSingleArm(study)) study@HR |
|
|
218 |
else calculateAverageHR(sfns,study,lagged@Lag.T,lagged@L.HazardRatio, |
|
|
219 |
lambdaot,lambda,study@shape) |
|
|
220 |
|
|
|
221 |
#Calculate the critical value |
|
|
222 |
critical.data <- CalculateCriticalValue(study,sfns,grid,av.HR) |
|
|
223 |
|
|
|
224 |
#Calculate events at given times |
|
|
225 |
predict.data <- if(!is.null(time.pred)) CalculateEventsGivenTimes(time.pred,study,sfns) |
|
|
226 |
else predict.data <- data.frame() |
|
|
227 |
|
|
|
228 |
|
|
|
229 |
#Calculate times for given number of events |
|
|
230 |
if(!is.null(event.pred)){ |
|
|
231 |
predict.data <- rbind(predict.data,CalculateTimesGivenEvents(event.pred,study,sfns,grid)) |
|
|
232 |
} |
|
|
233 |
|
|
|
234 |
return(AnalysisResults( |
|
|
235 |
critical.HR = critical.data[["chr"]], |
|
|
236 |
critical.data = critical.data[["critical.data"]], |
|
|
237 |
critical.events.req= critical.data[["critical.events.req"]], |
|
|
238 |
av.hr=av.HR, |
|
|
239 |
grid=grid, |
|
|
240 |
predict.data=predict.data, |
|
|
241 |
study=study, |
|
|
242 |
sfns=sfns)) |
|
|
243 |
}) |
|
|
244 |
|