-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMCPMod.R
More file actions
394 lines (372 loc) · 14.4 KB
/
MCPMod.R
File metadata and controls
394 lines (372 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
## wrapper function for MCTtest and fitMod calls
#' MCPMod - Multiple Comparisons and Modeling
#'
#' Tests for a dose-response effect using a model-based multiple contrast test (see [MCTtest()]), selects one
#' (or several) model(s) from the significant shapes, fits them using [fitMod()]. For details on the method
#' see \insertCite{bretz2005;textual}{DoseFinding}.
#'
#'
#' @aliases MCPMod predict.MCPMod plot.MCPMod
#' @inheritParams MCTtest
#' @param selModel Optional character vector specifying the model selection
#' criterion for dose estimation. Possible values are \itemize{ \item
#' `AIC`: Selects model with smallest AIC (this is the default) \item
#' `maxT`: Selects the model corresponding to the largest t-statistic.
#' \item `aveAIC`: Uses a weighted average of the models corresponding to
#' the significant contrasts. The model weights are chosen by the formula:
#' \eqn{w_i = \exp(-0.5AIC_i)/\sum_i(\exp(-0.5AIC_i))}{w_i =
#' exp(-0.5AIC_i)/sum(exp(-0.5AIC_i))} See \insertCite{buckland1997;textual}{DoseFinding} for details.
#' } For \samp{type = "general"} the "gAIC" is used.
#' @param df Specify the degrees of freedom to use in case \samp{type = "general"}, for the call to
#' [MCTtest()] and [fitMod()]. Infinite degrees of (\samp{df=Inf}) correspond to the multivariate
#' normal distribution. For type = "normal" the degrees of freedom deduced from the AN(C)OVA fit are used and this
#' argument is ignored.
#' @param doseType,Delta,p \samp{doseType} determines the dose to estimate, ED or TD (see also [Mods()]), and
#' \samp{Delta} and \samp{p} need to be specified depending on whether TD or ED is to be estimated. See
#' [TD()] and [ED()] for details.
#' @param bnds Bounds for non-linear parameters. This needs to be a list with list entries corresponding to the selected
#' bounds. The names of the list entries need to correspond to the model names. The [defBnds()] function
#' provides the default selection.
#' @param control Control list for the optimization.\cr A list with entries: "nlminbcontrol", "optimizetol" and
#' "gridSize".
#'
#' The entry nlminbcontrol needs to be a list and is passed directly to control argument in the nlminb function, that
#' is used internally for models with 2 nonlinear parameters (e.g. sigmoid Emax or beta model).
#'
#' The entry optimizetol is passed directly to the tol argument of the optimize function, which is used for models
#' with 1 nonlinear parameters (e.g. Emax or exponential model).
#'
#' The entry gridSize needs to be a list with entries dim1 and dim2 giving the size of the grid for the gridsearch in
#' 1d or 2d models.
#' @return An object of class \samp{MCPMod}, which contains the fitted \samp{MCTtest} object as well as the \samp{DRMod}
#' objects and additional information (model selection criteria, dose estimates, selected models).
#' @author Bjoern Bornkamp
#' @seealso [MCTtest()], [fitMod()], [drmodels()]
#' @references
#'
#' \insertRef{buckland1997}{DoseFinding}
#'
#' \insertRef{bretz2005}{DoseFinding}
#'
#' \insertRef{pinheiro2006b}{DoseFinding}
#'
#' \insertRef{pinheiro2006}{DoseFinding}
#'
#' \insertRef{pinheiro2014}{DoseFinding}
#'
#' \insertRef{schorning2016}{DoseFinding}
#'
#' \insertRef{seber2003}{DoseFinding}
#'
#' \insertRef{xun2017}{DoseFinding}
#'
#' @examples
#'
#' data(biom)
#' ## first define candidate model set (only need "standardized" models)
#' models <- Mods(linear = NULL, emax=c(0.05,0.2), linInt=c(1, 1, 1, 1),
#' doses=c(0,0.05,0.2,0.6,1))
#' plot(models)
#' ## perform MCPMod procedure
#' MM <- MCPMod(dose, resp, biom, models, Delta=0.5)
#' ## a number of things can be done with an MCPMod object
#' MM # print method provides basic information
#' summary(MM) # more information
#' ## predict all significant dose-response models
#' predict(MM, se.fit=TRUE, doseSeq=c(0,0.2,0.4, 0.9, 1),
#' predType="ls-means")
#' ## display all model functions
#' plot(MM, plotData="meansCI", CI=TRUE)
#'
#' ## now perform model-averaging
#' MM2 <- MCPMod(dose, resp, biom, models, Delta=0.5, selModel = "aveAIC")
#' sq <- seq(0,1,length=11)
#' pred <- predict(MM, doseSeq=sq, predType="ls-means")
#' modWeights <- MM2$selMod
#' ## model averaged predictions
#' pred <- do.call("cbind", pred)%*%modWeights
#' ## model averaged dose-estimate
#' TDEst <- MM2$doseEst%*%modWeights
#'
#' ## now an example using a general fit and fitting based on placebo
#' ## adjusted first-stage estimates
#' data(IBScovars)
#' ## ANCOVA fit model including covariates
#' anovaMod <- lm(resp~factor(dose)+gender, data=IBScovars)
#' drFit <- coef(anovaMod)[2:5] # placebo adjusted estimates at doses
#' vCov <- vcov(anovaMod)[2:5,2:5]
#' dose <- sort(unique(IBScovars$dose))[-1] # no estimate for placebo
#' ## candidate models
#' models <- Mods(emax = c(0.5, 1), betaMod=c(1,1), doses=c(0,4))
#' plot(models)
#' ## hand over placebo-adjusted estimates drFit to MCPMod
#' MM3 <- MCPMod(dose, drFit, S=vCov, models = models, type = "general",
#' placAdj = TRUE, Delta=0.2)
#' plot(MM3, plotData="meansCI")
#'
#' ## The first example, but with critical value handed over
#' ## this is useful, e.g. in simulation studies
#' MM4 <- MCPMod(dose, resp, biom, models, Delta=0.5, critV = 2.31)
#' @export
MCPMod <- function(dose, resp, data = NULL, models = NULL, S=NULL,
type = c("normal", "general"),
addCovars = ~1, placAdj = FALSE,
selModel = c("AIC", "maxT", "aveAIC"),
alpha = 0.025, df = NULL, critV = NULL,
doseType = c("TD", "ED"), Delta, p,
pVal = TRUE, alternative = c("one.sided", "two.sided"),
na.action = na.fail, mvtcontrol = mvtnorm.control(),
bnds, control = NULL){
direction <- attr(models, "direction")
## first perform multiple contrast test
if(!is.null(data)){
callMCT <- list(deparse(substitute(dose)), deparse(substitute(resp)), data,
models, S, type, addCovars, placAdj, alpha, df,
critV, pVal, alternative, na.action, mvtcontrol)
test <- do.call(MCTtest, callMCT)
} else {
test <- MCTtest(dose, resp, data, models, S, type, addCovars, placAdj, alpha, df,
critV, pVal, alternative, na.action, mvtcontrol)
}
## now pre-select models based on contrasts
tstat <- test$tStat
pvals <- attr(tstat, "pVal")
if(!is.null(pvals)){
tstat <- tstat[pvals < alpha]
} else {
tstat <- tstat[tstat > test$critVal]
}
if(length(tstat) == 0) ## stop if no model significant
return(list(MCTtest = test, mods = NULL, modcrit = NULL, selMod = NULL, TD = NULL))
## fit models and calculate model selection criteria
addArgs <- list(off=attr(models, "off"), scal=attr(models, "scal"))
selModel <- match.arg(selModel)
builtIn <- c("linlog", "linear", "quadratic", "linInt", "emax",
"exponential", "logistic", "betaMod", "sigEmax")
nams <- gsub("[0-9]", "", names(tstat)) ## remove numbers from model-names
namsU <- unique(nams)
mods <- vector("list", length(namsU));z <- 1
if(missing(bnds)){
if(!is.null(data)){
cal <- as.character(match.call())
doseVec <- data[, cal[2]]
} else {
doseVec <- dose
}
bnds <- defBnds(max(doseVec))
} else {
if(!is.list(bnds))
stop("bnds needs to be a list")
}
if(selModel %in% c("AIC", "aveAIC")){
if(type[1] == "normal"){
modcrit <- AIC
} else {
modcrit <- gAIC
}
} else {
modcrit <- function(x)
max(tstat[attr(x, "model") == nams])
}
for(i in 1:length(namsU)){
if(!is.null(data)){
callMod <- list(deparse(substitute(dose)), deparse(substitute(resp)), data,
namsU[i], S, type, addCovars, placAdj, bnds[[namsU[i]]],
df, NULL, na.action, control, addArgs)
mods[[i]] <- do.call(fitMod, callMod)
} else {
mods[[i]] <- fitMod(dose, resp, data, namsU[i], S, type, addCovars,
placAdj, bnds[[namsU[i]]], df, NULL,
na.action, control, addArgs)
}
}
crit <- sapply(mods, modcrit)
names(crit) <- names(mods) <- namsU
attr(crit, "crit") <- selModel
if(selModel %in% c("maxT", "AIC")){
if(selModel == "AIC"){
ind <- which.min(crit)
}
if(selModel == "maxT"){
nam <- names(tstat)[which.max(tstat)]
ind <- which(gsub("[0-9]", "", nam) == names(mods))
}
selMod <- namsU[ind] # name of selected model
} else {
aic <- crit-mean(crit)
selMod <- exp(-0.5*aic)/sum(exp(-0.5*aic)) # model weights
names(selMod) <- namsU
}
## calculate target dose estimate
tds <- NULL
doseType <- match.arg(doseType)
if(doseType == "TD"){
if(missing(Delta))
stop("\"Delta\" needs to be specified for TD estimation")
tds <- sapply(mods, TD, Delta=Delta, direction = direction)
attr(tds, "addPar") <- Delta
}
if(doseType == "ED"){
if(missing(p))
stop("\"p\" needs to be specified for TD estimation")
tds <- sapply(mods, ED, p=p)
attr(tds, "addPar") <- p
}
out <- list(MCTtest = test, mods = mods, modcrit=crit,
selMod=selMod, doseEst=tds, doseType = doseType)
class(out) <- "MCPMod"
out
}
#' Predict from the fitted dose-response model
#'
#' @param object,x MCPMod object
#' @param predType,newdata,doseSeq,se.fit,... predType determines whether predictions are returned for the full model
#' (including potential covariates), the ls-means (SAS type) or the effect curve (difference to placebo).
#'
#' newdata gives the covariates to use in producing the predictions (for \samp{predType = "full-model"}), if missing
#' the covariates used for fitting are used.
#'
#' doseSeq dose-sequence on where to produce predictions (for \samp{predType =
#' "effect-curve"} and \samp{predType = "ls-means"}). If missing the doses used
#' for fitting are used.
#'
#' se.fit: logical determining, whether the standard error should be calculated.
#'
#' \ldots: Additional arguments, for plot.MCPMod these are passed to plot.DRMod.
#'
#' @rdname MCPMod
#' @method predict MCPMod
#' @export
predict.MCPMod <- function(object,
predType = c("full-model", "ls-means", "effect-curve"),
newdata = NULL, doseSeq = NULL, se.fit = FALSE, ...){
lapply(object$mods, function(x) predict(x, predType, newdata, doseSeq, se.fit))
}
#' @export
print.MCPMod <- function(x, digits=3, eps=1e-03, ...){
cat("MCPMod\n")
xx <- x$MCTtest
cat("\nMultiple Contrast Test:\n")
ord <- rev(order(xx$tStat))
if (!any(is.null(attr(xx$tStat, "pVal")))) {
pval <- format.pval(attr(xx$tStat, "pVal"), digits = digits,
eps = eps)
dfrm <- data.frame(round(xx$tStat, digits)[ord], pval[ord])
names(dfrm) <- c("t-Stat", "adj-p")
}
else {
dfrm <- data.frame(round(xx$tStat, digits)[ord])
names(dfrm) <- c("t-Stat")
}
print(dfrm)
if (!is.null(xx$critVal)) {
twoSide <- xx$alternative == "two.sided"
vec <- c(" one-sided)", " two-sided)")
cat("\n", "Critical value: ", round(xx$critVal, digits),
sep = "")
if (attr(xx$critVal, "Calc")) {
cat(" (alpha = ", xx$alpha, ",", vec[twoSide + 1],
sep = "")
}
cat("\n")
}
cat("\n")
cat("Estimated Dose Response Models:")
for(i in 1:length(x$mods)){
cat("\n")
cat(names(x$mods)[i], "model\n")
cofList <- coef(x$mods[[i]], sep = TRUE)
cof <- do.call("c", cofList)
namcof <- c(names(cofList$DRpars), names(cofList$covarPars))
namcof <- gsub(" ", "", namcof) # remove white spaces for GUI
names(cof) <- gsub("doseM", "dose", namcof) # use more obvious names
print(round(cof, digits))
}
if(attr(x$modcrit, "crit") != "aveAIC"){
cat("\nSelected model (",attr(x$modcrit, "crit"),"): ", x$selMod, "\n", sep="")
} else {
cat("\nModel weights (AIC):\n")
attr(x$selMod, "crit") <- NULL
print(round(x$selMod, 4))
}
if(is.null(length(x$doseEst)))
return()
if(x$doseType == "TD")
strn <- ", Delta="
if(x$doseType == "ED")
strn <- ", p="
cat("\nEstimated ",x$doseType,strn,attr(x$doseEst, "addPar"),"\n", sep="")
attr(x$doseEst, "addPar") <- NULL
print(round(x$doseEst, 4))
}
#' @export
summary.MCPMod <- function(object, ...){
class(object) <- "summary.MCPMod"
print(object, digits = 3)
}
#' @export
print.summary.MCPMod <- function(x, ...){
cat("MCPMod\n\n")
cat(rep("*", 39), "\n", sep="")
cat("MCP part \n")
cat(rep("*", 39), "\n", sep="")
print(x$MCTtest)
cat("\n")
if(length(x$mods) == 0)
return()
cat(rep("*", 39), "\n", sep="")
cat("Mod part \n")
cat(rep("*", 39), "\n", sep="")
for(i in 1:length(x$mods)){
if(i > 1)
cat("\n")
if(length(x$mods) > 1)
cat("** Fitted model", i,"\n")
summary(x$mods[[i]])
}
cat("\n")
cat(rep("*", 39), "\n", sep="")
cat("Model selection criteria (",attr(x$modcrit, "crit"),"):\n", sep="")
cat(rep("*", 39), "\n", sep="")
crit <- attr(x$modcrit, "crit")
attr(x$modcrit, "crit") <- NULL
print(x$modcrit)
if(crit != "aveAIC"){
cat("\nSelected model:", x$selMod, "\n")
} else {
cat("\nModel weights (AIC):\n")
attr(x$selMod, "crit") <- NULL
print(round(x$selMod, 4))
}
if(is.null(length(x$doseEst)))
return()
cat("\n")
cat(rep("*", 39), "\n", sep="")
if(x$doseType == "TD")
strn <- ", Delta="
if(x$doseType == "ED")
strn <- ", p="
cat("Estimated ",x$doseType,strn,attr(x$doseEst, "addPar"),"\n", sep="")
cat(rep("*", 39), "\n", sep="")
attr(x$doseEst, "addPar") <- NULL
print(round(x$doseEst, 4))
}
#' Plot fitted dose-response model
#'
#' @inheritParams predict.MCPMod
#' @param CI,level,plotData,plotGrid,colMn,colFit Arguments for plot method: \samp{CI} determines whether confidence
#' intervals should be plotted. \samp{level} determines the level of the confidence intervals. \samp{plotData}
#' determines how the data are plotted: Either as means or as means with CI, raw data or none. In case of \samp{type =
#' "normal"} and covariates the ls-means are displayed, when \samp{type = "general"} the option "raw" is not
#' available. \samp{colMn} and \samp{colFit} determine the colors of fitted model and the raw means.
#'
#' @rdname MCPMod
#' @method plot MCPMod
#' @export
plot.MCPMod <- function(x, CI = FALSE, level = 0.95,
plotData = c("means", "meansCI", "raw", "none"),
plotGrid = TRUE, colMn = 1, colFit = 1, ...){
if(is.null(x$mods))
stop("No models significant, nothing to plot")
plotFunc(x, CI, level, plotData, plotGrid, colMn, colFit, ...)
}