# Design evaluation
## PK model user-defined
The equation corresponds to a one-compartment model with first-order absorption, with parameters ka, V and Cl. The dose is passed via the `dose_RespPK` keyword.
### Define the PK model equation
```{r, echo = TRUE, eval = FALSE, comment=''}
modelEquations = list(
"RespPK" = "dose_RespPK/V * ka/(ka - Cl/V) * (exp(-Cl/V * t) - exp(-ka * t))"
)
```
## Model parameters
The model has three structural parameters, all log-normally distributed. Inter-individual variability ($\omega$) and inter-occasion variability ($\gamma$) are specified for each.
| Parameter | Description | $\mu$ | $\omega$ | $\gamma$ | Fixed $\mu$ | Fixed $\omega$ |
|:----------|:------------------------------------|:-----:|:---------------------------|:-------------------------|:-----------:|:--------------:|
| *ka* | Absorption rate constant (h$^{-1}$) | 1 | $\sqrt{0.09} \approx 0.30$ | $\sqrt{0.0225} = 0.15$ | No | No |
| *V* | Volume of distribution (L) | 3.5 | $\sqrt{0.09} \approx 0.30$ | $\sqrt{0.0225} = 0.15$ | No | No |
| *Cl* | Elimination clearance (L/h) | 2 | $\sqrt{0.09} \approx 0.30$ | $\sqrt{0.0225} = 0.15$ | No | No |
### Define mu, omega and gamma for each parameter
```{r, echo = TRUE, eval = FALSE, comment=''}
modelParameters = list(
ModelParameter( name = "ka", distribution = LogNormal( mu = 1, omega = sqrt(0.09) ), gamma = sqrt(0.0225) ),
ModelParameter( name = "V", distribution = LogNormal( mu = 3.5, omega = sqrt(0.09) ), gamma = sqrt(0.0225) ),
ModelParameter( name = "Cl", distribution = LogNormal( mu = 2, omega = sqrt(0.09) ), gamma = sqrt(0.0225) )
)
```
## Residual error model
A constant (additive) residual error model is used, with `sigmaInter = 0.1` (variance = 0.01).
### Define the error model to the response PK `RespPK`
```{r, echo = TRUE, eval = FALSE, comment=''}
modelError = list( Constant( output = "RespPK", sigmaInter = 0.1 ) )
```
## Covariates
Covariate effects are parameterised on the log scale (`modelCovariatesEquation = "exponential"`), so each $\beta$ coefficient represents the log-ratio of the affected parameter between the non-reference and the reference category.
| Covariate | Type | Categories | Proportions | Affected parameter | Effect ($\beta$) | Reference |
|:----------|:--------------------------|:-----------|:------------|:-------------------|:-------------------------|:----------|
| Sex | Between-subject (fixed) | M / F | 50 % / 50 % | *V* | log(1.2) $\approx$ 0.182 | M |
| Treatment | Within-subject (occasion) | R / T | 50 % / 50 % | *Cl* | log(1.1) $\approx$ 0.095 | R |
**Sex** is a between-subject covariate with an exponential effect on `V`. The log-ratio between female and male typical values is `log(1.2)`.
### Define the between-subject covariate
```{r, echo = TRUE, eval = FALSE, comment=''}
sex = Covariate(
name = "Sex",
categories = c("M", "F"),
categoriesProportions = c(0.5, 0.5),
effects = list( "F" = c( "V" = log(1.2) ) )
)
```
**Treatment** is a within-subject (occasion) covariate following a two-sequence, two-period crossover design. The log-ratio of clearance under treatment T relative to treatment R is `log(1.1)`.
### Define the within-subject covariate
```{r, echo = TRUE, eval = FALSE, comment=''}
treatment = Covariate(
name = "Treatment",
categories = c("R", "T"),
sequences = list( c("R","T"), c("T","R") ),
sequencesProportions = c(0.5, 0.5),
effects = list( "T" = c( "Cl" = log(1.1) ) )
)
```
## Administration and sampling times
A single oral dose of 30 mg is administered at time 0. Five sampling times are specified to cover both the absorption and elimination phases.
### Define the administration parameters and the sampling times of the response PK
```{r, echo = TRUE, eval = FALSE, comment=''}
administrationRespPK = Administration( outcome = "RespPK", timeDose = c(0), dose = c(30) )
samplingTimesRespPK = SamplingTimes( outcome = "RespPK", samplings = c(0.5, 2, 4, 6, 8) )
```
## Arm and design
A single arm of 40 subjects on the same regimen.
### Define an arm called `arm1` of size 40 encompassed in the design `design1`
```{r, echo = TRUE, eval = FALSE, comment=''}
arm1 = Arm( name = "arm1",
size = 40,
administrations = list( administrationRespPK ),
samplingTimes = list( samplingTimesRespPK ) )
design1 = Design( name = "design1", arms = list( arm1 ) )
```
```{r ex03_run, include = FALSE}
script = system.file("vignette-scripts", "example03_execute.R", package = "PFIM")
if (!nzchar(script) || !file.exists(script))
stop("example03_execute.R not found; reinstall PFIM or rebuild vignettes.")
source(script, local = knitr::knit_global())
```
## Population FIM evaluation
The covariate effects use an exponential parameterisation (`modelCovariatesEquation = "exponential"`). The analytic model does not require ODE solver parameters.
### Evaluate the population FIM
```{r, echo = TRUE, eval = FALSE, comment=''}
evaluationPop = Evaluation(
name = "",
modelParameters = modelParameters,
modelCovariates = list( sex, treatment ),
modelCovariatesEquation = "exponential",
modelEquations = modelEquations,
modelError = modelError,
designs = list( design1 ),
fimType = "population",
outputs = list( "RespPK" = "RespPK" )
)
evaluationPopFIM = run( evaluationPop )
```
### Display the population FIM
```{r, echo = TRUE, eval = FALSE, results = "asis"}
show( evaluationPopFIM )
```
```{r ex03_show_pop, echo = FALSE, results = "asis"}
cat("", showOutputEvaluation, "
\n", sep = "")
```
```{r, echo = TRUE, eval = FALSE, fig.width = 10, fig.height = 5.5, out.width = "100%"}
plotsEval = plotEvaluation( evaluationPopFIM, plotOptions )
print( plotsEval$design1$arm1$RespPK )
```
```{r, echo = TRUE, eval = FALSE, fig.width = 10, fig.height = 5.5, out.width = "100%"}
plotsSI = plotSensitivityIndices( evaluationPopFIM, plotOptions )
print( plotsSI$design1$arm1$RespPK$V )
```
```{r, echo = TRUE, eval = FALSE, fig.width = 10, fig.height = 5.5, out.width = "100%"}
print( plotsSI$design1$arm1$RespPK$Cl )
```
```{r, echo = TRUE, eval = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%"}
plotEval_SE = PFIM::plotSE( evaluationPopFIM )
```
```{r ex03_plot_sedisp, echo = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%"}
plotEval_SE
```
```{r, echo = TRUE, eval = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%"}
plotEval_RSE = PFIM::plotRSE( evaluationPopFIM )
```
```{r ex03_plot_rsedisp, echo = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%"}
plotEval_RSE
```
```{r, echo = TRUE, eval = FALSE, comment=''}
outputFile = "vignette3_evaluation_popFim_report.html"
Report(evaluationPopFIM, paths$reports, outputFile, plotOptions)
```
Of note, we could also use these functions to extract specific results:
```{r, echo = TRUE, eval = FALSE, results = "asis"}
getFisherMatrix( evaluationPopFIM )
getCorrelationMatrix( evaluationPopFIM )
getSE( evaluationPopFIM )
getRSE( evaluationPopFIM )
getDeterminant( evaluationPopFIM )
getDcriterion( evaluationPopFIM )
```
# Covariate tests
The `covariateTest` function computes power for three hypothesis testing frameworks:
- **Significance test** --- $H_0$: $\beta = 0$ vs $H_1$: $\beta \neq 0$
- **Non-relevance test (TOST)** --- $H_0$: $|\beta| \geq \Delta$ vs $H_1$: $|\beta| < \Delta$ (effect is negligible)
- **Equivalence test** --- two-sided TOST assessing whether the effect lies within $[-\Delta, +\Delta]$
$\Delta$ is conventionally set to $\log(1.25) \approx 0.223$.
### Evaluate and display covariate tests
```{r, echo = TRUE, eval = FALSE, results = "asis"}
resultsTests = covariateTest( evaluationPopFIM )
show( resultsTests )
```
```{r, echo = FALSE, results = "asis"}
cat("", showOutputTests, "
\n", sep = "")
```
# Design optimization
Both optimization algorithms share the same model, error, covariates, and covariate equation as the evaluation step. Only the arm definition and the optimizer-specific parameters differ between the two approaches. The goal is to reduce the design to **3 sampling times** while maximising the D-criterion of the population FIM.
## Multiplicative algorithm
The Multiplicative Algorithm operates over a **discrete** candidate set: at each iteration it reweights a probability distribution over elementary designs (one per candidate time point) and prunes those with negligible weight. It is well-suited when the candidate set is finite and moderate in size.
### Define administration and sampling constraints
The dose is fixed at 30 mg. Three of the five candidate sampling times are left optimizable; no windows are imposed, so the algorithm selects freely among $\{0.5, 2, 4, 6, 8\}$ h.
```{r, echo = TRUE, eval = FALSE, comment=''}
administrationConstraintsRespPK = AdministrationConstraints(
outcome = "RespPK",
doses = list( 30 )
)
samplingConstraintsRespPK = SamplingTimeConstraints(
outcome = "RespPK",
initialSamplings = c( 0.5, 2, 4, 6, 8 ),
numberOfsamplingsOptimisable = 3
)
```
### Create the constraint arm and the associated design
The arm carries both `administrationsConstraints` and `samplingTimesConstraints`. The full set of candidate times is used as the initial sampling grid.
```{r, echo = TRUE, eval = FALSE, comment=''}
armMult = Arm( name = "armOpt",
size = 40,
administrations = list( administrationRespPK ),
samplingTimes = list( samplingTimesRespPK ),
administrationsConstraints = list( administrationConstraintsRespPK ),
samplingTimesConstraints = list( samplingConstraintsRespPK ) )
designMult = Design( name = "design1", arms = list( armMult ) )
```
### Set the parameters of the Multiplicative algorithm
```{r, echo = TRUE, eval = FALSE, comment=''}
optimizationMult = Optimization(
name = "Multiplicative",
modelEquations = modelEquations,
modelParameters = modelParameters,
modelCovariates = list( treatment, sex ),
modelCovariatesEquation = "exponential",
numberOfOccasions = 2,
modelError = modelError,
optimizer = "MultiplicativeAlgorithm",
optimizerParameters = list( lambda = 0.99,
numberOfIterations = 1000,
weightThreshold = 0.01,
delta = 1e-04,
showProcess = TRUE ),
designs = list( designMult ),
fimType = "population",
outputs = list( "RespPK" = "RespPK" )
)
```
### Run the Multiplicative algorithm for the optimization with a population FIM
```{r, echo = TRUE, eval = FALSE, comment=''}
optimizationMultPopFIM = run( optimizationMult )
```
### Display and plot Multiplicative algorithm results
```{r, echo = TRUE, eval = FALSE, results = "asis"}
show( optimizationMultPopFIM )
```
```{r ex03_show_mult, echo = FALSE, results = "asis", eval = .pfimVignetteHas("showOutputMult")}
cat("", showOutputMult, "
\n", sep = "")
```
```{r, echo = TRUE, eval = FALSE, comment=''}
plotMult_SE = PFIM::plotSE(optimizationMultPopFIM)
```
```{r ex02_plot_Mult_se, echo = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%", eval = .pfimVignetteHas("plotMult_SE")}
plotMult_SE
```
```{r, echo = TRUE, eval = FALSE, comment=''}
plotMult_RSE = PFIM::plotRSE(optimizationMultPopFIM)
```
```{r ex02_plot_Mult_rse, echo = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%", eval = .pfimVignetteHas("plotMult_RSE")}
plotMult_RSE
```
### Create and save the report for the design optimization
```{r, echo = TRUE, eval = FALSE, comment=''}
outputFile = "vignette3_optimization_Mult_populationFIM_report.html"
Report( optimizationMultPopFIM, paths$reports,outputFile, plotOptions)
```
## Simplex algorithm
The Simplex (Nelder-Mead) algorithm is a derivative-free local optimizer that searches over a **continuous** design space. It is more flexible than the Multiplicative algorithm but sensitive to the starting point and may converge to a local optimum.
### Define sampling constraints
Three sampling times are optimized continuously within $[0, 8]$ h, with a minimum spacing of 0.5 h between consecutive samples (`minSampling`). The initial design $\{0.5, 4, 8\}$ h seeds the starting simplex.
```{r, echo = TRUE, eval = FALSE, comment=''}
samplingTimesRespPK_simplex = SamplingTimes( outcome = "RespPK", samplings = c( 0.5, 4, 8 ) )
samplingConstraintsRespPK_simplex = SamplingTimeConstraints(
outcome = "RespPK",
initialSamplings = c( 0.5, 4, 8 ),
samplingsWindows = list( c(0, 8) ),
numberOfTimesByWindows = c(3),
minSampling = c(0.5)
)
```
### Create the constraint arm and the associated design
No administration constraints are needed here since the dose is fixed. The arm uses the Simplex-specific initial samplings and constraints.
```{r, echo = TRUE, eval = FALSE, comment=''}
armSimplex = Arm( name = "armOpt",
size = 40,
administrations = list( administrationRespPK ),
samplingTimes = list( samplingTimesRespPK_simplex ),
samplingTimesConstraints = list( samplingConstraintsRespPK_simplex ) )
designSimplex = Design( name = "design1", arms = list( armSimplex ) )
```
### Set the parameters of the Simplex algorithm
```{r, echo = TRUE, eval = FALSE, comment=''}
optimizationSimplex = Optimization(
name = "Simplex",
modelEquations = modelEquations,
modelParameters = modelParameters,
modelCovariates = list( treatment, sex ),
modelCovariatesEquation = "exponential",
modelError = modelError,
optimizer = "SimplexAlgorithm",
optimizerParameters = list( pctInitialSimplexBuilding = 20,
maxIteration = 200,
tolerance = 1e-6,
showProcess = TRUE ),
designs = list( designSimplex ),
fimType = "population",
outputs = list( "RespPK" = "RespPK" )
)
```
### Run the Simplex algorithm for the optimization with a population FIM
```{r, echo = TRUE, eval = FALSE, comment=''}
optimizationSimplexPopFIM = run( optimizationSimplex )
```
### Display and plot Simplex results
```{r, echo = TRUE, eval = FALSE, results = "asis"}
show( optimizationSimplexPopFIM )
```
```{r ex03_show_spx, echo = FALSE, results = "asis", eval = .pfimVignetteHas("showOutputSimplex")}
cat("", showOutputSimplex, "
\n", sep = "")
```
```{r, echo = TRUE, eval = FALSE, comment=''}
plotSimplex_SE = PFIM::plotSE(optimizationSimplexPopFIM)
```
```{r ex03_plot_Simplex_se, echo = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%", eval = .pfimVignetteHas("plotSimplex_SE")}
plotSimplex_SE
```
```{r, echo = TRUE, eval = FALSE, comment=''}
plotSimplex_RSE = PFIM::plotRSE(optimizationSimplexPopFIM)
```
```{r ex03_plot_Simplex_rse, echo = FALSE, fig.width = 11, fig.height = 4.4, out.width = "100%", eval = .pfimVignetteHas("plotSimplex_RSE")}
plotSimplex_RSE
```
### Create and save the report for the design optimization
```{r, echo = TRUE, eval = FALSE, comment=''}
outputFile = "vignette3_optimization_Simplex_populationFIM_report.html"
Report( optimizationSimplexPopFIM, paths$reports, outputFile, plotOptions)
```
# Covariate tests on Simplex optimal design
```{r, echo = TRUE, eval = FALSE, results = "asis"}
optimisationDesign = prop( optimizationSimplexPopFIM, "optimisationDesign" )
evaluationOptimalDesign = optimisationDesign$evaluationOptimalDesign
optimalTests = covariateTest( evaluationOptimalDesign )
show( optimalTests )
```
```{r ex03_show_optimal_tests, echo = FALSE, results = "asis"}
cat("", showOutputOptimalTests, "
\n", sep = "")
```
Using 3-point optimal design leads to only a slight loss of power, with N = 145 subjects required to achieve 90% power on the significance of the sex effect on V, instead of N = 141 on 5-point initial design. It also requires N = 26 subjects to assess clinical non-relevance of the treatment on Cl with 90% power, instead of N = 25 on 5-point initial design.
```{r global_options_end, echo = FALSE, include = FALSE}
options(backup_options)
```