microsamplingDesign

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

\clearpage{}

knitr::opts_chunk$set(fig.width=12 )

Introduction

Microsampling, a novel blood sampling technique allows multiple blood samples to be taken per animal, reducing the number of animals required for pharmacokinetic-pharmacodynamic studies (@chapman2014overcoming). Using sparce designs can in addition, avoid unnecessary sampling of these animals, provided an appropriate choice of sample times per animals is made. The microsamplingDesign package implements a general simulation methodology to find optimal sparse microsampling schemes aimed at non-compartmental pharmacokinetic analysis (algorithm III in @barnettOD). This methodology consist of (1) specifying a pharmacokinetic model including variability among animals; (2) generating possible sampling times; (3) evaluating performance of each time point choice on simulated data; (4) generating possible schemes given a time point choice and additional constraints and finally (5) evaluating scheme perfomance on simulated data. The default settings differ from (@barnettOD)) in the default pharmacokinetic model used and the parameterization of variability among animals (see next section). A shiny web application is included, which guides users from model parametrization to optimal microsampling scheme.

Model details

A two compartmental oral dosing pharamcokinetic model (@gabrielsson2001pharmacokinetic) is assumed:

$$ \frac{d D_g}{dt} = - k_a.D_g $$ $$ V_c \frac{dC}{dt} = F.k_a .D_g - Cl.C - Cl_d.C + Cl_d.C_t $$ $$ V_t \frac{dC_t}{dt} = Cl_d.C - Cl_d.C_t $$

A dose of a substance ($D_g$) is administered to the gut, than graduadely absorbed into a central compartment leading to a increased concentration in the plasma ($C$). Where it can either be excreted or exchanged with a second peripheral compartment, the peripheral tissues, where the compound has a distinct concentration ( $C_t$) in time, depending on the rate of exchange with the central compartment. We do not assume any excretion from the peripheral compartment.

Substance absorption and clearance are by default assumed to be capacity dependent (Michaelis-Menten kinetics):

$$k_a = \frac{V_{a,max} }{ \kappa {a,m} + D_g }$$ $$Cl = \frac{V{e,max} }{ \kappa _{e,m} + C }$$

We also leave the option open for one or both of these parameters to be constant.

For details see (@gabrielsson2001pharmacokinetic).

Parametrization

Log-normal parameters

Individual animals are assumed to have the same underlying model, with different parameters simulated from an underlying log-normal distribution parametrized in terms of the mean and the coefficient of variation.

we assume a random variable $X$ to be log-normally distributed with parameters $\mu$ and $\sigma$:

$$X = \exp{ \big( \mu + \sigma Z \big) }$$ with $Z$ a standard normal variable.

Now, we want to extract $\mu$ and $\sigma$ from and coefficient of variation ($CV = sd(X)/E(X)$) of the original scale.

we can use the relation for the mean:

$$E(X) = \exp{ \big( \mu + \frac{\sigma^2}{2} } \big) $$

and the relation for the coefficient of variation: $$CV(X) = \sqrt{ \exp{\sigma^2} - 1 }$$

Therefore: $$\sigma = \sqrt{ ln( CV^2 + 1) }$$

and $$\mu = \ln \big( E(X) \big) - \frac{\sigma^2}{2} $$

For the multivariate log-normal distribution, we use a the same approach per variable and can simulate a random vector: $$\boldsymbol{X} = \exp{ \big( \boldsymbol{\mu} + Z \boldsymbol{\sigma^T} \big) }$$

with $Z \sim \mathcal{N} ( \boldsymbol{0} , \Sigma )$ and $\Sigma$ a specified correlation matrix. More information see (@halliwell2015lognormal)

\pagebreak{}

microsamplingDesign shiny application

Before diving into the R code of the microsamplingDesign package, we give a more intuitive introduction to the methodology using the included shiny application. In a local R session we can start the application:

library( microsamplingDesign )
runMicrosamplingDesignApp( installDependencies = TRUE )

The first time you want to run the application, use installDependencies = TRUE to automatically install the additional R-package required for this shiny application in addition to the microsamplingDesign package dependencies.

Construct a pharmacokinetic model

Start the application by constructing a pharacokinetic model.

```r knitr::include_graphics( "appPictures/modifyParameters.png" )

Example parameters are shown on start up. To modify these parameters click on
**Modify parameters** and a spreadsheet is displayed allowing modifying
parameter values and their coefficient of variation (see Figure
\@ref(fig:constructModel)).

Next include dosing information by filling out one or several lines, click on
**Generate example curves** to check simulated time-concentration curves (see
Figure \@ref(fig:sampleCurves)).

```r
knitr::include_graphics( "appPictures/sampleCurves.png" )

One can adapt the scale of the graphs by clicking on Graphical settings.

Note that the pharmacokinetic model in the application does not contain any measurement error.

Generate possible time points

Time point options are generated from a time constraints table specifying the number of time points per time zone and minimum sampling interval in each row. Note that the endTime is not included in the zone itself but is the startTime of the next zone.

knitr::include_graphics( "appPictures/generateTimePoints.png" )

Finally click on the button Generate time points, to recieve all possible combinations in table form (see Figure \@ref(fig:generateTime)).

Rank time points

Time points options are ranked by measuring the difference between approximating the average time-concentration curve based on a limited number of time points on sample data and the actual average curve at the maximal number of time points you want to consider. This is a measure of bias caused by choosing a certain time point option rather then sampling at the maximum number of time points.

In the application ranking time points takes 2 steps:

Generate sample data

knitr::include_graphics( "appPictures/rankTimePoints1.png" )

Specify the approximate number of animals you would like to use in you scheme and the number of simulated datasets to generate. Then press Generate data to rank time points. A selection of simulated data will be displayed (see Figure \@ref(fig:rankTime1)).

Rank time points

knitr::include_graphics( "appPictures/rankTimePoints2.png" )

After checking the generatated data, click on Rank time points to estimate the bias of each time point option. Calculations might take a few minutes, depending on the the number of simulation samples and time point options. When calculations are finished, time point options are tabulated from small to large deviation from the best accuracy. You can select a time point option by clicking on a row in the time point ranking table (see Figure \@ref(fig:rankTime2)).

Generate possible schemes

Given the time points, we will construct schemes specifying which subjects are sampled at which time points.

knitr::include_graphics( "appPictures/generateSchemes.png" )

To generate these schemes, fill out the scheme's dimensions and the maximum number of repetitions of individual schemes. You can already assess the possible number of schemes by clicking on Check number of schemes before constraints wich is much faster then generating the schemes first. Reconsider scheme dimensions when the number of schemes is too large. The possible number of schemes can also be cut down by imposing scheme constraints. Finally click on Generate schemes to receive all schemes meeting constraints. This might take a few minutes (see Figure \@ref(fig:generateSchemes)).

Rank schemes

Schemes are ranked by their precision of estimating the area under the curve (AUC) and maximum concentration (Cmax) on simulated data.

Again we work in 2 steps:

Generate sample data

knitr::include_graphics( "appPictures/rankSchemes1.png" )

Generate data by specifying the number of simulation samples and click Generate data to rank schemes (see Figure \@ref(fig:rankSchemes1)).

Rank schemes

knitr::include_graphics( "appPictures/rankSchemes2.png" )

After data generation, specify the objective function by attaching a relative importance to different non-compartmental statistics and click on Rank schemes (see Figure \@ref(fig:rankSchemes2)). This might take some time.

Finally select a scheme by clicking on the Scheme ranking table.

When a final scheme is chosen, first click on Generate report and next on Download report to recieve a word document summarizing the main results.

Finding optimal designs using code

Settings

settings                <-  list()
settings$nSamples       <-  100 # increase for real life example 
set.seed(124)

Construct a pharmacokinetic model

library( microsamplingDesign )
pkModel                <- getExamplePkModel()

some useful functions:

modelParameters      <-  getParameters( pkModel ) 
knitr::kable( modelParameters[ , c(1:2) ] )

To generate your own pharmacokinitic model see:

?construct2CompModel

Generate time points

Possible time points are generated from a full set of time points:

fullTimePointsEx     <-  seq( 0 , 16 , 0.5 )
print( fullTimePointsEx )

With the choice of options constraints by timeZones:

#timeZonesEx         <-  getExampleTimeZones()
timeZonesEx          <-  data.frame( startTime = c( 0 , 2 , 3 ) ,
  endTime = c( 2 , 3 , 16 ) ,
  nPointsPerZone = c( 2 , 1 , 2 )  )
knitr::kable( timeZonesEx )

timeZones concept is defined such that : time zero is never included, last timePoint is always included.

Correct names should be used!

Now we can generate all time point options from a vector of possible time points under constraints defined in timeZones:

setOfTimePoints          <-  getAllTimeOptions( timeZones = timeZonesEx ,
    fullTimePoints = fullTimePointsEx )
# ?SetOfTimePoints   # class definition
#str( setOfTimePoints ) # to see all slots in the example
slotNames( setOfTimePoints  )

knitr::kable( head( getData( setOfTimePoints) ) )
knitr::kable( tail( getData( setOfTimePoints) ) )

note 0 never chosen , time 16 always included

Rank time points

To rank the timePoint options inside a SetOfTimePoints object , we first need to simulate PkData.

model               <-  getExamplePkModel() 
fullTimePoints      <-  getTimePoints( setOfTimePoints )
pkDataForTimePoints <-  getPkData( pkModel = model , timePoints = fullTimePoints ,
  nSubjectsPerScheme = 5 , nSamples = settings$nSamples  ) 
plotObject( pkDataForTimePoints , nCurves = 5 )

This is just small number of samples, in reality one would use a larger number such as 1000.

We can than use the rank function to find the optimal time points:

rankedTimePoints      <-  rankObject( setOfTimePoints , pkData = pkDataForTimePoints , 
        nGrid =  150 , nSamplesAvCurve = settings$nSamples ) 
rankingTimePoints     <-  getRanking( rankedTimePoints )
knitr::kable( head( rankingTimePoints ) )
#knitr::kable( tail( rankingTimePoints ) )
indTimeChoice         <-  getTopNRanking( rankingTimePoints , 1  )
bestTimeChoice        <-  setOfTimePoints[ indTimeChoice ,  ]
bestTimeChoice

Generate possible schemes

timePointsChoice      <-  bestTimeChoice

To generate schemes we can define additional constraints:

constraintsExample    <-  getConstraintsExample()[c( 2 , 4 ) , ]
knitr::kable( constraintsExample )

Constraints are defined on 2 levels: subject or scheme.

setOfSchemes          <-  getSetOfSchemes( minNSubjects = 4 , maxNSubjects = 5 , 
    minObsPerSubject = 4 , maxObsPerSubject = 5  , 
    timePoints =  timePointsChoice , constraints =  constraintsExample ,  
    maxRepetitionIndSchemes = 1 , maxNumberOfSchemesBeforeChecks = 10^8  )  
slotNames( setOfSchemes ) 

The number of combinations can get get very large especially with maxRepetitionIndSchemes > 1.

Rank schemes

To rank schemes, we need matching Pkdata (number of animals and timePoints):

timePointsEx    <-  getTimePoints( setOfSchemes )
pkData          <-  getPkData( pkModel, timePoints = timePointsEx ,
  nSubjectsPerScheme = 5 , nSamples = settings$nSamples )
plotObject( pkData , nCurves = 7 , addZeroIsZero = TRUE )

To rank schemes, we have to define an objective function, based on the a scheme based statistic ( AUC , ... ) a weight representing its relative importance.

exampleObjective      <-   data.frame( 
  criterion = c( "auc" , "cMax" , "tMax" )  ,
  weight = c( 9 , 1, 1 ) ) 
knitr::kable( exampleObjective )

But be carefull cMax and tMax might be very variable when multiple doses are administered.

setOfSchemesRanked    <-  rankObject(setOfSchemes , pkData = pkData ,
  objective = exampleObjective , varianceMeasure = "var" , scaleWith  = "max" )
schemeRanking         <-  getRanking( setOfSchemesRanked )
knitr::kable( head(  schemeRanking ) )
knitr::kable( tail( schemeRanking ) )

indTopSchemes        <-  getTopNRanking( schemeRanking , nSelect = 1 )
indBottomSchemes     <-  getTopNRanking( schemeRanking , nSelect = 1 , top = FALSE )
bestScheme           <-  setOfSchemesRanked[ , , indTopSchemes ]
knitr::kable( bestScheme )
worstScheme          <-  setOfSchemesRanked[ , , indBottomSchemes ]
knitr::kable( worstScheme ) 

Advanced options

Parallelization

Parallelization by forking is supported on linux machines and can be used to seed up simulating pkData, generating or ranking timepoints or schemes. You need to specify the number of cores inside these functions (nCores):

setOfSchemesRanked            <-  rankObject(setOfSchemes , pkData = pkData ,
objective = exampleObjective ,      varianceMeasure = "var" , scaleWith  = "max" , 
 nCores = 2 )

Working with ranges

Using ranges of parameters is also supported, see

?rankObjectWithRange

for details.

Memo of main functions

Data generation

Generate and rank time points

Generate and rank schemes

References



Try the microsamplingDesign package in your browser

Any scripts or data that you put into this service are public.

microsamplingDesign documentation built on Oct. 13, 2021, 5:10 p.m.