using Dates
using Pumas
using PumasUtilities
using DataFramesMeta
using PharmaDatasets
using CairoMakie
using AlgebraOfGraphics
using Random
Why are non-Gaussian random effects relevant?
1 Motivation - PK model
Why using a non-Gaussian distribution as the underlying distribution for the random effects? There are a couple of arguments.
First, the Gaussian distribution has unbounded support, i.e. it take any value in \((-\infty, \infty)\). While phamacokinetic parameters typically are (semi) bounded, e.g.:
- clearance and volumes, \((0, \infty)\)
- bioavailability, \([0, 1]\)
Additionally, in order for a Gaussian distribution to work as the underlying distribution, often we need to transform them (e.g. exponentiation and logistic transformation). But these transformations in some settings, when the random effects do not have a great impact, i.e. they do not have large values, may shift the mean of the typical values (\(\theta\)) so that the expectation of the typical values (\(\operatorname{E}\)) are not equal to the mean. For example, the following code block is a traditional 1-compartment PK model with a Gaussian random effect that needs to be constrained to positive values, \((0, \infty)\):
@random begin
~ Normal(0.0, ωCL)
ηCL ~ Normal(0.0, ωVc)
ηVc end
@pre begin
= θCL * exp(ηCL)
CL = θVc * exp(ηVc)
Vc end
@dynamics begin
' = -CL / Vc * Central
Centralend
If we recover the formula for the expectation of the log-normal distribution, we have that:
\[\operatorname{E}[CL] = \exp \left\{ \log(\theta_{CL}) + \frac{\omega^2_{CL}}{2} \right\} \approx \theta_{CL}\]
This approximation only holds for small \(\omega_{CL}\).
Hence, \(\theta_{CL}\) is only the typical value when \(\omega_{CL}\) is small.
Here is a small tabulation for \(\operatorname{E}[CL]\) when \(\theta_{CL} = 0.5\):
= [0.1, 0.2, 0.4, 0.8, 1.6]
ωs DataFrame(; ω_CL = ωs, E_CL = (ω -> exp(log(0.5) + ω^2 / 2)).(ωs))
Row | ω_CL | E_CL |
---|---|---|
Float64 | Float64 | |
1 | 0.1 | 0.502506 |
2 | 0.2 | 0.510101 |
3 | 0.4 | 0.541644 |
4 | 0.8 | 0.688564 |
5 | 1.6 | 1.79832 |
As you can see, the larger the \(\omega_{CL}\) the more \(\operatorname{E}[CL]\) deviates from \(\theta_{CL}\).
1.1 Gamma distribution for the rescue
We can use the gamma distribution which has the following parametrization:
\[\text{Gamma}(k, \theta)\]
where \(k\) is a shape parameter and \(\theta\) is a scale parameter.
Shape parameters generally control the shape of the distribution rather than shifting it (as a location parameter) of stretching/shrinking it (as a scale parameter)
We can use an alternative parametrization where the mean-value appears directly a parameter:
\[\text{Gamma}(\mu, \sigma)\]
where:
- \(\mu = \theta k\)
- \(\sigma = k^{-\frac{1}{2}}\)
The \(\sigma\) parameter is the coefficient of variation, i.e.
\[\sigma = \frac{\operatorname{Var} X}{\operatorname{E} X},\]
because that mimics the role of \(\sigma\) in the LogNormal(log(μ), σ)
where for small values of \(\sigma\)
\[\sigma \approx \\frac{\operatorname{Var} X}{\operatorname{E} X}.\]
So, our previous PK model now becomes:
@random begin
~ Gamma(1 / ωCL^2, θCL * ωCL^2)
_CL ~ Gamma(1 / ωVc^2, θVc * ωVc^2)
_Vc end
@pre begin
= _CL
CL = _Vc
Vc end
@dynamics begin
' = -CL / Vc * Central
Centralend
As you can see the mean from the gamma distribution becomes:
\[\operatorname{E}[CL] = \theta k = \frac{1}{\omega^2_{CL}} \theta_{CL} \omega^2_{CL} = \theta_{CL}\]
It does not dependent on the between-subject variability \(\omega\)!
We are avoiding η
notation here since we are modeling the subject-specific parameter directly.
1.2 Gamma versus Log-Nogmal Numerical Simulations
Before we dive into our PK examples, let us showcase the case for gamma versus log-normal with some numerical simulations.
First, let’s define a mean μ_PK
value for a typical value along with an array of possible standard deviations σ
values:
= 1.0
μ_PK = [0.1, 0.2, 0.5, 1.0, 1.5, 2.0] σ
These will serve as the mean and standard deviations for our gamma and log-normal distributions.
Now let’s compare the coefficient of variation (CV) as a function of σ
for LogNormal
and Gamma
:
= DataFrame(;
num_df_gamma = μ_PK,
μ = σ,
σ = mean.(LogNormal.(log.(μ_PK), σ)),
meanLogNormal = std.(LogNormal.(log.(μ_PK), σ)) ./ mean.(LogNormal.(log.(μ_PK), σ)),
cvLogNormal = mean.(Gamma.(1 ./ σ .^ 2, μ_PK .* σ .^ 2)),
meanGamma = std.(Gamma.(1 ./ σ .^ 2, μ_PK .* σ .^ 2)) ./
cvGamma mean.(Gamma.(1 ./ σ .^ 2, μ_PK .* σ .^ 2)),
)
Row | μ | σ | meanLogNormal | cvLogNormal | meanGamma | cvGamma |
---|---|---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
1 | 1.0 | 0.1 | 1.00501 | 0.100251 | 1.0 | 0.1 |
2 | 1.0 | 0.2 | 1.0202 | 0.202017 | 1.0 | 0.2 |
3 | 1.0 | 0.5 | 1.13315 | 0.53294 | 1.0 | 0.5 |
4 | 1.0 | 1.0 | 1.64872 | 1.31083 | 1.0 | 1.0 |
5 | 1.0 | 1.5 | 3.08022 | 2.91337 | 1.0 | 1.5 |
6 | 1.0 | 2.0 | 7.38906 | 7.32108 | 1.0 | 2.0 |
= lines(
f, ax, plotobj
num_df_gamma.σ,
num_df_gamma.meanLogNormal;= "μ - LogNormal",
label = 3,
linewidth = (; xlabel = L"\sigma", ylabel = "value"),
axis
)lines!(
num_df_gamma.σ,
num_df_gamma.meanGamma;= "μ - Gamma",
label = 3,
linewidth = :dashdot,
linestyle
)lines!(num_df_gamma.σ, num_df_gamma.cvLogNormal; label = "CV - LogNormal", linewidth = 3)
lines!(
num_df_gamma.σ,
num_df_gamma.cvGamma;= "CV - Gamma",
label = 3,
linewidth = :dashdot,
linestyle
)axislegend(ax; position = :lt)
f
In the graph above, the dashed lines correspond to the mean and CV for the gamma distribution, whereas the solid lines correspond to the log-normal distribution.
There is clearly a bias in both the log-normal’s mean and CV that we don’t see in the gamma distribution.
2 Motivation - Bioavailability
Here is a very common model that can benefit from a non-Gaussian random effects distribution.
The model has one-compartment elimination and oral absorption with modeled bioavailability based on a crossover design.
The following code is a traditional PK model with a Gaussian random effect that needs to be constrained to the unit interval, \([0, 1]\):
@param begin
∈ RealDomain(lower = 0.0, upper = 1.0)
θF ∈ RealDomain(lower = 0.0)
ωF end
@random begin
~ Normal(0.0, ωF)
ηF end
@dosecontrol begin
= (Depot = logistic(logit(θF) + ηF),)
bioav end
The expectation \(\operatorname{E}[F]\) doesn’t have closed form and is generally different from \(\theta_F\). However, we have that:
\[\operatorname{E}[F] \approx \theta_F\]
when \(ωF\) is small. I.e. \(\theta_F\) is only the typical value when \(ωF\) is small.
2.1 Beta versus Logit-Normal Numerical Simulations
Let’s perform the same type of simulations we did before, but now we will be using the numerical integrator quadgk
from the QuadGK.jl
package. This is because we don’t have a closed form solution for \(\operatorname{E}[F]\) in the logit-normal parameterization.
using QuadGK: quadgk
= 0.7 μ_bioav
We’ll also reuse the same σ
values for the CVs.
= DataFrame(;
num_df_beta = μ_bioav,
μ = σ,
σ = map(
meanLogitNormal -> quadgk(
σ -> logistic(t) * pdf(Normal(logit(μ_bioav), σ), t),
t -100 * σ,
100 * σ,
1],
)[
σ,
),= mean.(Beta.(μ_bioav ./ σ, (1 - μ_bioav) ./ σ)),
meanBeta )
Row | μ | σ | meanLogitNormal | meanBeta |
---|---|---|---|---|
Float64 | Float64 | Float64 | Float64 | |
1 | 0.7 | 0.1 | 0.699582 | 0.7 |
2 | 0.7 | 0.2 | 0.698345 | 0.7 |
3 | 0.7 | 0.5 | 0.690393 | 0.7 |
4 | 0.7 | 1.0 | 0.668971 | 0.7 |
5 | 0.7 | 1.5 | 0.646064 | 0.7 |
6 | 0.7 | 2.0 | 0.626038 | 0.7 |
= lines(
f, ax, plotobj
num_df_beta.σ,
num_df_beta.meanLogitNormal;= "μ - LogitNormal",
label = 3,
linewidth = (; xlabel = L"\sigma", ylabel = "value"),
axis
)lines!(
num_df_beta.σ,
num_df_beta.meanBeta;= "μ - Beta",
label = 3,
linewidth = :dashdot,
linestyle
)axislegend(ax; position = :lb)
f
In the graph above, the dashed lines correspond to the mean for the beta distribution, whereas the solid lines correspond to the logit-normal distribution.
As before, there is clearly a bias in the logit-normal’s mean that we don’t see in the beta distribution.
3 Warfarin data
We’ll demonstrate those intuitions using the Warfarin dataset.
= read_pumas(dataset("pumas/warfarin")) pop
Population
Subjects: 32
Observations: dv
4 Models and Simulations
Here we will provide a Gaussian and a non-Gaussian approach for:
- PK IV 1-compartment model fit for the Warfarin dataset
- Bioavaliability parallel absorption model simulation
4.1 Warfarin Gaussian and non-Gaussian PK model
The first model is a simple 1-compartment PK IV model with proportional error. This is for the Gaussian versus gamma random effects:
= @model begin
model_lognormal @param begin
∈ RealDomain(; lower = 0)
θCL ∈ RealDomain(; lower = 0)
θVc
∈ RealDomain(; lower = 0)
ωCL ∈ RealDomain(; lower = 0)
ωVc
∈ RealDomain(; lower = 0)
σ end
@random begin
~ LogNormal(log(θCL), ωCL)
_CL ~ LogNormal(log(θVc), ωVc)
_Vc end
# This is equivalent to defining
# CL = θCL*exp(ηCL)
# with
# ηCL = Normal(0, ωCL)
@pre begin
= _CL
CL = _Vc
Vc end
@dynamics Central1
@derived begin
:= @. Central / Vc
μ ~ @. Normal(μ, μ * σ)
dv end
end
PumasModel
Parameters: θCL, θVc, ωCL, ωVc, σ
Random effects: _CL, _Vc
Covariates:
Dynamical system variables: Central
Dynamical system type: Closed form
Derived: dv
Observed: dv
= @model begin
model_gamma @param begin
∈ RealDomain(; lower = 0)
θCL ∈ RealDomain(; lower = 0)
θVc
∈ RealDomain(; lower = 0)
ωCL ∈ RealDomain(; lower = 0)
ωVc
∈ RealDomain(; lower = 0)
σ end
@random begin
~ Gamma(1 / ωCL^2, θCL * ωCL^2)
_CL ~ Gamma(1 / ωVc^2, θVc * ωVc^2)
_Vc end
@pre begin
= _CL
CL = _Vc
Vc end
@dynamics begin
' = -CL / Vc * Central
Centralend
@derived begin
:= @. Central / Vc
μ ~ @. Normal(μ, μ * σ)
dv end
end
PumasModel
Parameters: θCL, θVc, ωCL, ωVc, σ
Random effects: _CL, _Vc
Covariates:
Dynamical system variables: Central
Dynamical system type: Matrix exponential
Derived: dv
Observed: dv
We also need some initial values for the fitting:
= (; θCL = 1.0, θVc = 5.0, ωCL = 0.1, ωVc = 0.1, σ = 0.2) iparams_pk
(θCL = 1.0,
θVc = 5.0,
ωCL = 0.1,
ωVc = 0.1,
σ = 0.2,)
We proceed by fitting both models:
= fit(model_lognormal, pop, iparams_pk, FOCE()) fit_lognormal
[ Info: Checking the initial parameter values.
[ Info: The initial negative log likelihood and its gradient are finite. Check passed.
Iter Function value Gradient norm
0 5.770212e+03 7.912060e+03
* time: 0.02042984962463379
1 9.433464e+02 6.079483e+02
* time: 0.8397860527038574
2 8.189627e+02 4.423725e+02
* time: 0.8657889366149902
3 5.917683e+02 1.819248e+02
* time: 0.8857529163360596
4 5.421783e+02 1.121313e+02
* time: 0.9018678665161133
5 5.255651e+02 7.407230e+01
* time: 0.9165918827056885
6 5.208427e+02 8.699271e+01
* time: 0.9299800395965576
7 5.174883e+02 8.974584e+01
* time: 0.9430439472198486
8 5.138523e+02 7.328235e+01
* time: 0.9557769298553467
9 5.109883e+02 4.155805e+01
* time: 0.9680249691009521
10 5.094359e+02 3.170517e+01
* time: 1.005375862121582
11 5.086172e+02 3.327331e+01
* time: 1.0168569087982178
12 5.080941e+02 2.942077e+01
* time: 1.0283479690551758
13 5.074009e+02 2.839941e+01
* time: 1.0397119522094727
14 5.059302e+02 3.330093e+01
* time: 1.0505728721618652
15 5.036399e+02 3.172884e+01
* time: 1.0623178482055664
16 5.017004e+02 3.160020e+01
* time: 1.0745270252227783
17 5.008553e+02 2.599524e+01
* time: 1.0867209434509277
18 5.005913e+02 2.139314e+01
* time: 1.098296880722046
19 5.003573e+02 2.134778e+01
* time: 1.1096160411834717
20 4.997249e+02 2.069868e+01
* time: 1.121438980102539
21 4.984453e+02 1.859010e+01
* time: 1.1334278583526611
22 4.959584e+02 2.156209e+01
* time: 1.1456449031829834
23 4.923347e+02 3.030833e+01
* time: 1.158552885055542
24 4.906916e+02 1.652278e+01
* time: 1.1724109649658203
25 4.902955e+02 6.360800e+00
* time: 1.1852660179138184
26 4.902870e+02 7.028603e+00
* time: 1.1991069316864014
27 4.902193e+02 1.176895e+00
* time: 1.2316389083862305
28 4.902189e+02 1.170642e+00
* time: 1.2420358657836914
29 4.902186e+02 1.167624e+00
* time: 1.2509489059448242
30 4.902145e+02 1.110377e+00
* time: 1.2619140148162842
31 4.902079e+02 1.010507e+00
* time: 1.2728948593139648
32 4.901917e+02 9.619218e-01
* time: 1.2841930389404297
33 4.901683e+02 1.001006e+00
* time: 1.2943298816680908
34 4.901473e+02 6.138233e-01
* time: 1.3071980476379395
35 4.901412e+02 1.754342e-01
* time: 1.3189139366149902
36 4.901406e+02 2.617009e-02
* time: 1.329360008239746
37 4.901405e+02 4.585882e-03
* time: 1.3385679721832275
38 4.901405e+02 7.668184e-04
* time: 1.3461809158325195
FittedPumasModel
Successful minimization: true
Likelihood approximation: FOCE
Likelihood Optimizer: BFGS
Dynamical system type: Closed form
Log-likelihood value: -490.14052
Number of subjects: 32
Number of parameters: Fixed Optimized
0 5
Observation records: Active Missing
dv: 256 0
Total: 256 0
-----------------
Estimate
-----------------
θCL 0.16025
θVc 10.262
ωCL 0.23505
ωVc 0.10449
σ 0.3582
-----------------
= fit(model_gamma, pop, iparams_pk, FOCE()) fit_gamma
[ Info: Checking the initial parameter values.
[ Info: The initial negative log likelihood and its gradient are finite. Check passed.
Iter Function value Gradient norm
0 4.160707e+03 5.960578e+03
* time: 5.602836608886719e-5
1 9.056373e+02 5.541920e+02
* time: 0.02894306182861328
2 7.787963e+02 3.988904e+02
* time: 0.1283550262451172
3 5.915054e+02 1.495777e+02
* time: 0.1464521884918213
4 5.533120e+02 8.826583e+01
* time: 0.16358208656311035
5 5.389239e+02 9.144086e+01
* time: 0.17986607551574707
6 5.323499e+02 1.000933e+02
* time: 0.1958010196685791
7 5.270252e+02 8.423844e+01
* time: 0.2110581398010254
8 5.233813e+02 5.194402e+01
* time: 0.22545504570007324
9 5.213366e+02 3.461331e+01
* time: 0.2401599884033203
10 5.200972e+02 3.888113e+01
* time: 0.2547750473022461
11 5.191933e+02 3.556605e+01
* time: 0.2692751884460449
12 5.181335e+02 3.624436e+01
* time: 0.28419017791748047
13 5.161626e+02 4.322775e+01
* time: 0.29912614822387695
14 5.133202e+02 3.722515e+01
* time: 0.31336212158203125
15 5.107758e+02 3.401586e+01
* time: 0.3279430866241455
16 5.095157e+02 2.854997e+01
* time: 0.34288597106933594
17 5.090165e+02 2.644560e+01
* time: 0.3566451072692871
18 5.085184e+02 2.744429e+01
* time: 0.36995506286621094
19 5.074309e+02 2.793918e+01
* time: 0.3829841613769531
20 5.053757e+02 2.616169e+01
* time: 0.39652299880981445
21 5.018507e+02 2.257667e+01
* time: 0.45429515838623047
22 4.942495e+02 3.832878e+01
* time: 0.4710509777069092
23 4.940229e+02 5.518159e+01
* time: 0.49210619926452637
24 4.909110e+02 3.042064e+01
* time: 0.512160062789917
25 4.900234e+02 6.929306e+00
* time: 0.5299201011657715
26 4.897974e+02 1.087865e+00
* time: 0.5490851402282715
27 4.897942e+02 6.456402e-01
* time: 0.5645411014556885
28 4.897940e+02 6.467689e-01
* time: 0.5785460472106934
29 4.897939e+02 6.463480e-01
* time: 0.5926971435546875
30 4.897935e+02 6.408914e-01
* time: 0.607105016708374
31 4.897924e+02 6.208208e-01
* time: 0.6217780113220215
32 4.897900e+02 1.035462e+00
* time: 0.6368839740753174
33 4.897850e+02 1.452099e+00
* time: 0.6521401405334473
34 4.897776e+02 1.482593e+00
* time: 0.6705591678619385
35 4.897718e+02 8.420646e-01
* time: 0.6862890720367432
36 4.897702e+02 2.023876e-01
* time: 0.7013580799102783
37 4.897700e+02 1.885486e-02
* time: 0.7151050567626953
38 4.897700e+02 2.343932e-03
* time: 0.7274811267852783
39 4.897700e+02 4.417566e-04
* time: 0.7507190704345703
FittedPumasModel
Successful minimization: true
Likelihood approximation: FOCE
Likelihood Optimizer: BFGS
Dynamical system type: Matrix exponential
Log-likelihood value: -489.77002
Number of subjects: 32
Number of parameters: Fixed Optimized
0 5
Observation records: Active Missing
dv: 256 0
Total: 256 0
-----------------
Estimate
-----------------
θCL 0.16466
θVc 10.329
ωCL 0.23348
ωVc 0.10661
σ 0.35767
-----------------
Finally, let’s compare the estimates:
compare_estimates(; lognormal = fit_lognormal, gamma = fit_gamma)
Row | parameter | lognormal | gamma |
---|---|---|---|
String | Float64? | Float64? | |
1 | θCL | 0.160253 | 0.164658 |
2 | θVc | 10.2617 | 10.3288 |
3 | ωCL | 0.235046 | 0.233484 |
4 | ωVc | 0.10449 | 0.106611 |
5 | σ | 0.358205 | 0.357667 |
As mention above, the mean of a log-normal is \(\exp \left\{ \mu + \frac{\sigma^2}{2} \right\}\).
So let’s compare that with the gamma typical values:
DataFrame(;
= ["θCL", "θVc"],
parameter = [coef(fit_lognormal).θCL, coef(fit_lognormal).θVc],
θLogNormal = [
ELogNormal exp(log(coef(fit_lognormal).θCL) + coef(fit_lognormal).ωCL^2 / 2),
exp(log(coef(fit_lognormal).θVc) + coef(fit_lognormal).ωVc^2 / 2),
],= [coef(fit_gamma).θCL, coef(fit_gamma).θVc],
θGamma )
Row | parameter | θLogNormal | ELogNormal | θGamma |
---|---|---|---|---|
String | Float64 | Float64 | Float64 | |
1 | θCL | 0.160253 | 0.164741 | 0.164658 |
2 | θVc | 10.2617 | 10.3178 | 10.3288 |
As you can see the Gaussian model has a slight bias in the estimation of both θCL
and θVc
.
Let’s also plot the two probability density functions (PDF) for θCL
:
= @chain DataFrame(; x = range(0, 0.5; length = 1_000)) begin
plotdataPK @rtransform begin
:LogNormal =
pdf(LogNormal(log(coef(fit_lognormal).θCL), coef(fit_lognormal).ωCL), :x)
:Gamma = pdf(LogNormal(log(coef(fit_gamma).θCL), coef(fit_gamma).ωCL), :x)
end
end
first(plotdataPK, 5)
Row | x | LogNormal | Gamma |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.0 | 0.0 | 0.0 |
2 | 0.000500501 | 5.27258e-128 | 5.2502e-131 |
3 | 0.001001 | 9.25648e-99 | 3.24235e-101 |
4 | 0.0015015 | 2.10132e-83 | 1.45529e-85 |
5 | 0.002002 | 2.71651e-73 | 2.9785e-75 |
data(stack(plotdataPK, [:LogNormal, :Gamma])) *
mapping(:x, :value; color = :variable) *
visual(Lines) |> draw
4.2 Bioavaliability Parallel Absorption Simulation
This is a parallel absorption model with bioavaliabity in both the “fast” as the “slow” depots.
First, the traditional approach with a logistic transformation of a Gaussian random variable. This makes the individual relative bioavailibility logit-normally distributed.
= @model begin
model_logitnormal @param begin
∈ RealDomain(; lower = 0)
θkaFast ∈ RealDomain(; lower = 0)
θkaSlow ∈ RealDomain(; lower = 0)
θCL ∈ RealDomain(; lower = 0)
θVc ∈ RealDomain(; lower = 0.0, upper = 1.0)
θbioav
∈ RealDomain(; lower = 0)
ωCL ∈ RealDomain(; lower = 0)
ωVc # I call this one ξ to distinguish it from ω since the interpretation is NOT a relative error (coefficient of variation)
∈ RealDomain(; lower = 0, init = 0.1)
ξbioav
∈ RealDomain(; lower = 0)
σ end
@random begin
~ Gamma(1 / ωCL^2, θCL * ωCL^2)
_CL ~ Gamma(1 / ωVc^2, θVc * ωVc^2)
_Vc # define the latent Gaussian random effect. Notice the logit transform
~ Normal(logit(θbioav), ξbioav)
ηbioavLogit end
@pre begin
= θkaFast
kaFast = θkaSlow
kaSlow = _CL
CL = _Vc
Vc end
@dosecontrol begin
# _bioav is LogitNormal distributed
= logistic(ηbioavLogit)
_bioav = (; DepotFast = _bioav, DepotSlow = 1 - _bioav)
bioav end
@dynamics begin
' = -kaFast * DepotFast
DepotFast' = -kaSlow * DepotSlow
DepotSlow' = kaFast * DepotFast + kaSlow * DepotSlow - CL / Vc * Central
Centralend
@derived begin
:= @. Central / Vc
μ ~ @. Normal(μ, abs(μ) * σ)
dv end
end
PumasModel
Parameters: θkaFast, θkaSlow, θCL, θVc, θbioav, ωCL, ωVc, ξbioav, σ
Random effects: _CL, _Vc, ηbioavLogit
Covariates:
Dynamical system variables: DepotFast, DepotSlow, Central
Dynamical system type: Matrix exponential
Derived: dv
Observed: dv
Now the same model but with the non-Gaussian random-effects using a beta distribution instead of the logit parameterization of the Gaussian distribution:
= @model begin
model_beta @param begin
∈ RealDomain(; lower = 0)
θkaFast ∈ RealDomain(; lower = 0)
θkaSlow ∈ RealDomain(; lower = 0)
θCL ∈ RealDomain(; lower = 0)
θVc ∈ RealDomain(; lower = 0.0, upper = 1.0)
θbioav
∈ RealDomain(; lower = 0)
ωCL ∈ RealDomain(; lower = 0)
ωVc # We call this one n since the interpretation is like the length of a Binomial distribution
∈ RealDomain(; lower = 0, init = 10)
nbioav
∈ RealDomain(; lower = 0)
σ end
@random begin
~ Gamma(1 / ωCL^2, θCL * ωCL^2)
ηCL ~ Gamma(1 / ωVc^2, θVc * ωVc^2)
ηVc # The makes E(_bioav) = θbioav
# See https://en.wikipedia.org/wiki/Beta_distribution
~ Beta(θbioav * nbioav, (1 - θbioav) * nbioav)
ηbioav end
@pre begin
= θkaFast
kaFast = θkaSlow
kaSlow = ηCL
CL = ηVc
Vc end
@dosecontrol begin
= (; DepotFast = ηbioav, DepotSlow = 1 - ηbioav)
bioav end
@dynamics begin
' = -kaFast * DepotFast
DepotFast' = -kaSlow * DepotSlow
DepotSlow' = kaFast * DepotFast + kaSlow * DepotSlow - CL / Vc * Central
Centralend
@derived begin
:= @. Central / Vc
μ ~ @. Normal(μ, abs(μ) * σ)
dv end
end
PumasModel
Parameters: θkaFast, θkaSlow, θCL, θVc, θbioav, ωCL, ωVc, nbioav, σ
Random effects: ηCL, ηVc, ηbioav
Covariates:
Dynamical system variables: DepotFast, DepotSlow, Central
Dynamical system type: Matrix exponential
Derived: dv
Observed: dv
We have two types of random effects here.
First, as you are already familiar from the previous example, the clearance (CL
), volume of concentration (Vc
), and absorption rate (ka
) have typical values (i.e. fixed effects) and between-subject variability (i.e. random effects) modelled as a gamma distribution.
Second, bioavailability Bioav
is modelled as a beta distribution. Generally the beta distribution is parametrized as:
\[\text{Beta}(\alpha, \beta)\]
where both parameters \(\alpha\) and \(\beta\) are shape parameters.
One nice thing about the beta distribution is that it only takes values between and including 0 and 1, i.e. \([0, 1]\). This makes it the perfect candidate to model bioavailability parameters which are generally bounded in that interval. So, we don’t need to do a logistic transformation.
Another nice thing about the beta distribution is that we can use the alternative \((\mu, n)\)-parametrization with with \(\mu\) serving as a mean-value parameter:
\[\text{Beta}(\mu, n)\]
where in the original beta parametrization:
- \(\alpha = \mu n\)
- \(\beta = (1 - \mu) n\)
Hence, our mean is:
\[\operatorname{E}[F] = \mu = \theta_F\]
which, again, does not depend on any other parameters. The variance is
\[\operatorname{Var}(F) = \frac{\mu(1 - \mu)}{n}\]
so similar to the mean of Bernoulli trials.
Now let’s generate some data for the simulation:
= DosageRegimen(
dr DosageRegimen(100; cmt = :DepotFast),
DosageRegimen(100; cmt = :DepotSlow),
)
Row | time | cmt | amt | evid | ii | addl | rate | duration | ss | route |
---|---|---|---|---|---|---|---|---|---|---|
Float64 | Symbol | Float64 | Int8 | Float64 | Int64 | Float64 | Float64 | Int8 | NCA.Route | |
1 | 0.0 | DepotFast | 100.0 | 1 | 0.0 | 0 | 0.0 | 0.0 | 0 | NullRoute |
2 | 0.0 | DepotSlow | 100.0 | 1 | 0.0 | 0 | 0.0 | 0.0 | 0 | NullRoute |
= [0.5, 1.0, 2.0, 4.0, 8.0, 24.0] simtimes
6-element Vector{Float64}:
0.5
1.0
2.0
4.0
8.0
24.0
= (;
trueparam = 0.9,
θkaFast = 0.2,
θkaSlow = 1.1,
θCL = 10.0,
θVc = 0.7,
θbioav = 0.1,
ωCL = 0.1,
ωVc = 40,
nbioav = 0.1,
σ )
(θkaFast = 0.9,
θkaSlow = 0.2,
θCL = 1.1,
θVc = 10.0,
θbioav = 0.7,
ωCL = 0.1,
ωVc = 0.1,
nbioav = 40,
σ = 0.1,)
For simplicity, we just add 20% to the true values for initial values:
= map(t -> 1.2 * t, trueparam) initparamBeta
(θkaFast = 1.08,
θkaSlow = 0.24,
θCL = 1.32,
θVc = 12.0,
θbioav = 0.84,
ωCL = 0.12,
ωVc = 0.12,
nbioav = 48.0,
σ = 0.12,)
The initial values for the LogitNormal
need to have ξbioav
defined instead of nbioav
:
=
initparamLogitNormal Base.structdiff(initparamBeta, NamedTuple{(:nbioav,)})..., ξbioav = 0.1) (
(θkaFast = 1.08,
θkaSlow = 0.24,
θCL = 1.32,
θVc = 12.0,
θbioav = 0.84,
ωCL = 0.12,
ωVc = 0.12,
σ = 0.12,
ξbioav = 0.1,)
Setup empty Subject
s with the dose information:
= map(i -> Subject(; id = i, events = dr), 1:40) skeletonpop
Population
Subjects: 40
Observations:
Next, we simulate the data (while setting the seed for reprocibility):
=
simpop Subject.(
simobs(
model_beta,
skeletonpop,
trueparam;= simtimes,
obstimes = Random.seed!(Random.default_rng(), 128),
rng
) )
Finally let’s fit both models:
= fit(model_logitnormal, simpop, initparamLogitNormal, FOCE()) fit_logitnormal
[ Info: Checking the initial parameter values.
[ Info: The initial negative log likelihood and its gradient are finite. Check passed.
Iter Function value Gradient norm
0 2.512964e+02 3.650794e+02
* time: 6.580352783203125e-5
1 2.005689e+02 1.252637e+02
* time: 0.27806782722473145
2 1.875728e+02 4.225518e+01
* time: 0.40436387062072754
3 1.863803e+02 3.429785e+01
* time: 0.5180678367614746
4 1.845581e+02 3.694229e+01
* time: 0.6123218536376953
5 1.828416e+02 1.170915e+01
* time: 0.7706289291381836
6 1.823277e+02 1.004017e+01
* time: 0.8625869750976562
7 1.810629e+02 5.326780e+00
* time: 0.9559628963470459
8 1.810479e+02 1.767987e+00
* time: 1.0455639362335205
9 1.810272e+02 3.852037e+00
* time: 1.140915870666504
10 1.809414e+02 1.196625e+01
* time: 1.2429289817810059
11 1.806489e+02 1.861440e+01
* time: 1.348660945892334
12 1.801984e+02 1.361774e+01
* time: 1.764153003692627
13 1.800427e+02 2.177039e+01
* time: 1.8610119819641113
14 1.796554e+02 7.079039e+00
* time: 1.9564948081970215
15 1.795832e+02 1.581499e+01
* time: 2.060091972351074
16 1.795220e+02 6.552120e+00
* time: 2.164411783218384
17 1.794621e+02 6.612645e+00
* time: 2.2678539752960205
18 1.793643e+02 6.603903e+00
* time: 2.378734827041626
19 1.793502e+02 1.025787e+01
* time: 2.513934850692749
20 1.793178e+02 1.202199e+01
* time: 2.6095688343048096
21 1.792424e+02 1.625661e+01
* time: 2.7060768604278564
22 1.791560e+02 1.128856e+01
* time: 2.8015809059143066
23 1.790991e+02 7.625637e+00
* time: 2.9023327827453613
24 1.790756e+02 8.557258e+00
* time: 3.00312876701355
25 1.790633e+02 4.848482e+00
* time: 3.1059207916259766
26 1.790408e+02 5.859306e+00
* time: 3.207359790802002
27 1.789734e+02 1.106035e+01
* time: 3.3109359741210938
28 1.789070e+02 1.143361e+01
* time: 3.434027910232544
29 1.788321e+02 7.098448e+00
* time: 3.529693841934204
30 1.787896e+02 5.709857e+00
* time: 3.624307870864868
31 1.787809e+02 7.456555e+00
* time: 3.7208139896392822
32 1.787671e+02 7.320931e-01
* time: 3.82366681098938
33 1.787660e+02 5.023990e-01
* time: 3.926435947418213
34 1.787656e+02 3.813994e-01
* time: 4.0245139598846436
35 1.787639e+02 8.608909e-01
* time: 4.123638868331909
36 1.787607e+02 2.047321e+00
* time: 4.248727798461914
37 1.787525e+02 3.859529e+00
* time: 4.338005781173706
38 1.787349e+02 5.864920e+00
* time: 4.429638862609863
39 1.787017e+02 6.966045e+00
* time: 4.5223047733306885
40 1.786574e+02 5.348939e+00
* time: 4.622135877609253
41 1.786270e+02 1.642025e+00
* time: 4.722083806991577
42 1.786181e+02 5.211823e-01
* time: 4.820537805557251
43 1.786153e+02 1.186061e+00
* time: 4.919969797134399
44 1.786125e+02 1.292005e+00
* time: 5.015107870101929
45 1.786099e+02 7.814598e-01
* time: 5.131313800811768
46 1.786086e+02 1.369456e-01
* time: 5.221433877944946
47 1.786082e+02 1.912170e-01
* time: 5.3089189529418945
48 1.786080e+02 2.670802e-01
* time: 5.3984479904174805
49 1.786078e+02 1.979262e-01
* time: 5.493316888809204
50 1.786077e+02 5.177918e-02
* time: 5.58785080909729
51 1.786076e+02 2.998328e-02
* time: 5.683206796646118
52 1.786076e+02 5.799706e-02
* time: 5.7755959033966064
53 1.786076e+02 4.280434e-02
* time: 5.869516849517822
54 1.786076e+02 1.467829e-02
* time: 5.986361980438232
55 1.786076e+02 6.928178e-03
* time: 6.066188812255859
56 1.786076e+02 1.236015e-02
* time: 6.150690793991089
57 1.786076e+02 9.950826e-03
* time: 6.234149932861328
58 1.786076e+02 2.974370e-03
* time: 6.322774887084961
59 1.786076e+02 1.374576e-03
* time: 6.414756774902344
60 1.786076e+02 2.860078e-03
* time: 6.4990479946136475
61 1.786076e+02 2.100127e-03
* time: 6.58353590965271
62 1.786076e+02 2.100127e-03
* time: 6.7107977867126465
63 1.786076e+02 6.412834e-03
* time: 6.834594964981079
64 1.786076e+02 6.412834e-03
* time: 6.9713218212127686
65 1.786076e+02 6.412834e-03
* time: 7.156803846359253
66 1.786076e+02 6.412834e-03
* time: 7.444414854049683
67 1.786076e+02 6.412834e-03
* time: 7.665327787399292
FittedPumasModel
Successful minimization: true
Likelihood approximation: FOCE
Likelihood Optimizer: BFGS
Dynamical system type: Matrix exponential
Log-likelihood value: -178.60759
Number of subjects: 40
Number of parameters: Fixed Optimized
0 9
Observation records: Active Missing
dv: 240 0
Total: 240 0
-----------------------
Estimate
-----------------------
θkaFast 0.91097
θkaSlow 0.13112
θCL 1.0854
θVc 7.1008
θbioav 0.4802
ωCL 0.088113
ωVc 0.12133
ξbioav 1.8429e-5
σ 0.10545
-----------------------
= fit(model_beta, simpop, initparamBeta, FOCE()) fit_beta
[ Info: Checking the initial parameter values.
[ Info: The initial negative log likelihood and its gradient are finite. Check passed.
Iter Function value Gradient norm
0 2.523111e+02 3.644346e+02
* time: 5.316734313964844e-5
1 2.014577e+02 1.265001e+02
* time: 0.2949249744415283
2 1.880885e+02 4.190708e+01
* time: 0.44132208824157715
3 1.870317e+02 8.825666e+01
* time: 0.5463001728057861
4 1.846027e+02 4.400156e+01
* time: 0.6741721630096436
5 1.834445e+02 1.906624e+01
* time: 0.8503191471099854
6 1.828599e+02 1.113882e+01
* time: 0.9424631595611572
7 1.815719e+02 7.449355e+00
* time: 1.0367910861968994
8 1.815131e+02 2.164678e+00
* time: 1.1308059692382812
9 1.814896e+02 2.167319e+00
* time: 1.2327191829681396
10 1.814458e+02 4.615738e+00
* time: 1.3339321613311768
11 1.813173e+02 9.576967e+00
* time: 1.4360170364379883
12 1.809756e+02 2.052077e+01
* time: 1.5425829887390137
13 1.807625e+02 4.553366e+01
* time: 1.6894891262054443
14 1.802224e+02 6.550892e+00
* time: 1.7825870513916016
15 1.800862e+02 2.865509e+00
* time: 1.8777351379394531
16 1.800780e+02 1.164611e+00
* time: 1.9698760509490967
17 1.800737e+02 7.952462e-01
* time: 2.065891981124878
18 1.800352e+02 4.860618e+00
* time: 2.171504020690918
19 1.800089e+02 5.176689e+00
* time: 2.2761080265045166
20 1.799679e+02 4.303892e+00
* time: 2.381277084350586
21 1.799153e+02 4.612832e+00
* time: 2.487298011779785
22 1.798423e+02 1.209387e+01
* time: 2.612706184387207
23 1.796821e+02 1.712256e+01
* time: 2.709073066711426
24 1.794275e+02 1.435100e+01
* time: 2.8036751747131348
25 1.793773e+02 4.137313e+00
* time: 2.8986880779266357
26 1.793488e+02 1.846545e+00
* time: 3.0291590690612793
27 1.793331e+02 5.502533e+00
* time: 3.1314611434936523
28 1.793234e+02 2.894037e+00
* time: 3.2332980632781982
29 1.793119e+02 1.453372e+00
* time: 3.3321101665496826
30 1.792879e+02 4.109884e+00
* time: 3.4589531421661377
31 1.792599e+02 4.613173e+00
* time: 3.551457166671753
32 1.792384e+02 4.254549e+00
* time: 3.643778085708618
33 1.792251e+02 4.415024e+00
* time: 3.7352311611175537
34 1.792026e+02 3.229145e+00
* time: 3.8345530033111572
35 1.791841e+02 3.422094e+00
* time: 3.9347541332244873
36 1.791705e+02 1.621228e+00
* time: 4.037853002548218
37 1.791555e+02 3.462667e+00
* time: 4.135825157165527
38 1.791293e+02 5.586685e+00
* time: 4.236175060272217
39 1.790713e+02 9.927638e+00
* time: 4.3542890548706055
40 1.789891e+02 1.133271e+01
* time: 4.447052001953125
41 1.788585e+02 1.279172e+01
* time: 4.536435127258301
42 1.787407e+02 5.291681e+00
* time: 4.62983512878418
43 1.786633e+02 5.367971e+00
* time: 4.733007192611694
44 1.786405e+02 2.571548e+00
* time: 4.830978155136108
45 1.786339e+02 2.720314e+00
* time: 4.93030309677124
46 1.786252e+02 1.930583e+00
* time: 5.029952049255371
47 1.786182e+02 1.523164e+00
* time: 5.155141115188599
48 1.786126e+02 5.027920e-01
* time: 5.245054006576538
49 1.786100e+02 3.733023e-01
* time: 5.332525014877319
50 1.786089e+02 2.749553e-01
* time: 5.42055606842041
51 1.786083e+02 1.981772e-01
* time: 5.508686065673828
52 1.786079e+02 1.005262e-01
* time: 5.6030189990997314
53 1.786078e+02 2.549641e-02
* time: 5.698633193969727
54 1.786077e+02 4.452931e-02
* time: 5.790536165237427
55 1.786076e+02 2.967125e-02
* time: 5.883052110671997
56 1.786076e+02 1.068274e-02
* time: 5.975605010986328
57 1.786076e+02 5.355447e-03
* time: 6.0869550704956055
58 1.786076e+02 8.180920e-03
* time: 6.170389175415039
59 1.786076e+02 4.935439e-03
* time: 6.251726150512695
60 1.786076e+02 4.387986e-03
* time: 6.351366996765137
61 1.786076e+02 4.388023e-03
* time: 6.496145009994507
62 1.786076e+02 4.387934e-03
* time: 6.625404119491577
63 1.786076e+02 4.387934e-03
* time: 6.733436107635498
FittedPumasModel
Successful minimization: true
Likelihood approximation: FOCE
Likelihood Optimizer: BFGS
Dynamical system type: Matrix exponential
Log-likelihood value: -178.60759
Number of subjects: 40
Number of parameters: Fixed Optimized
0 9
Observation records: Active Missing
dv: 240 0
Total: 240 0
----------------------
Estimate
----------------------
θkaFast 0.91099
θkaSlow 0.13112
θCL 1.0854
θVc 7.1007
θbioav 0.48019
ωCL 0.088114
ωVc 0.12134
nbioav 2.7333e7
σ 0.10545
----------------------
As before, let’s compare the estimates:
compare_estimates(; logitnormal = fit_logitnormal, beta = fit_beta)
Row | parameter | logitnormal | beta |
---|---|---|---|
String | Float64? | Float64? | |
1 | θkaFast | 0.910971 | 0.910988 |
2 | θkaSlow | 0.131115 | 0.131117 |
3 | θCL | 1.0854 | 1.0854 |
4 | θVc | 7.10076 | 7.10073 |
5 | θbioav | 0.480202 | 0.480191 |
6 | ωCL | 0.0881132 | 0.0881137 |
7 | ωVc | 0.121334 | 0.121335 |
8 | σ | 0.105448 | 0.105449 |
9 | ξbioav | 1.84292e-5 | missing |
10 | nbioav | missing | 2.73333e7 |
Again, we’ll both PDFs from the estimated values:
= @chain DataFrame(; x = range(0, 1; length = 1_000)) begin
plotdatabioav @rtransform begin
:logitnormal =
1 / coef(fit_logitnormal).ξbioav / √(2π) / (:x * (1 - :x)) * exp(
-(logit(:x) - logit(coef(fit_logitnormal).θbioav))^2 /
2 * coef(fit_logitnormal).ξbioav^2),
(
):beta = pdf(
Beta(
coef(fit_beta).θbioav * coef(fit_beta).nbioav,
1 - coef(fit_beta).θbioav) * coef(fit_beta).nbioav,
(
),:x,
)end
end
first(plotdatabioav, 5)
Row | x | logitnormal | beta |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 0.0 | NaN | 0.0 |
2 | 0.001001 | 0.0 | 0.0 |
3 | 0.002002 | 0.0 | 0.0 |
4 | 0.003003 | 0.0 | 0.0 |
5 | 0.004004 | 0.0 | 0.0 |
=
plt_pdf_bioav data(stack(plotdatabioav, [:logitnormal, :beta])) *
mapping(:x, :value; color = :variable) *
visual(Lines);
draw(plt_pdf_bioav; axis = (; xticks = 0.1:0.1:1.0))
For this dataset, the two distributions differ significantly with the Beta model producing a distribution much closer to the truth but for other realizations of the simulated data they are closer to each other.