| Title: | Structural and Practical Identifiability for Pharmacokinetic and Pharmacodynamic Models |
|---|---|
| Description: | Determines which parameters of pharmacokinetic and pharmacodynamic ordinary differential equation models can be identified from a given dosing and observation design, and which added measurement would make the others identifiable. Decisions are local and are stated as such. |
| Authors: | Daiki Kumakura [aut, cre] |
| Maintainer: | Daiki Kumakura <[email protected]> |
| License: | GPL-3 |
| Version: | 0.1.0 |
| Built: | 2026-10-19 20:15:48 UTC |
| Source: | https://github.com/DaikiKumakura/pkident |
Extract result tables
## S3 method for class 'pkident_designs' as_table(x, what = c("summary", "parameters"), ...) ## S3 method for class 'pkident_practical' as_table(x, what = c("parameters", "directions", "correlation"), ...) ## S3 method for class 'pkident_profiles' as_table(x, what = c("parameters", "profiles"), ...) as_table(x, what, ...) ## S3 method for class 'pkident_structural' as_table(x, what = c("parameters", "combinations", "rank", "agreement"), ...)## S3 method for class 'pkident_designs' as_table(x, what = c("summary", "parameters"), ...) ## S3 method for class 'pkident_practical' as_table(x, what = c("parameters", "directions", "correlation"), ...) ## S3 method for class 'pkident_profiles' as_table(x, what = c("parameters", "profiles"), ...) as_table(x, what, ...) ## S3 method for class 'pkident_structural' as_table(x, what = c("parameters", "combinations", "rank", "agreement"), ...)
x |
A pkident result. |
what |
Which table. |
... |
Unused. |
A tibble.
Describe how a known dose enters the model.
bolus_dose(state, amount = "DOSE") infusion_dose(state, rate = "RATE")bolus_dose(state, amount = "DOSE") infusion_dose(state, rate = "RATE")
state |
Name of the state that receives the dose. |
amount |
Amount at time zero, as a character expression. |
rate |
Zero-order input rate, as a character expression. |
bolus_dose() sets the initial amount of a state (an instantaneous dose at
time zero), for example bolus_dose("depot", "F*DOSE").
infusion_dose() adds a known zero-order input rate to a state, for example
infusion_dose("central", "RATE").
Symbols used in amount or rate that are not states become parameters
unless they are declared as known in pkpd_model().
An object of class pkident_dose.
bolus_dose("depot", "F*DOSE") infusion_dose("central", "RATE")bolus_dose("depot", "F*DOSE") infusion_dose("central", "RATE")
Fixes each selected population parameter at values on either side of its
estimate, re-estimates all other parameters with
nlmixr2extra::profileFixed(), and classifies the change in objective
function value (dOFV) following Raue et al. (2009, Bioinformatics
25:1923-1929):
classify_profiles( fit, which = NULL, grid = NULL, threshold = 3.84, steps = c(0.1, 0.25, 0.5, 1, 2), flat_tol = 1, structural = NULL, map = NULL )classify_profiles( fit, which = NULL, grid = NULL, threshold = 3.84, steps = c(0.1, 0.25, 0.5, 1, 2), flat_tol = 1, structural = NULL, map = NULL )
fit |
An nlmixr2 fit (FOCEi or another estimation method supported by
|
which |
Names of population parameters (thetas) to profile. By default all estimated thetas that are not residual error parameters. |
grid |
Optional named list of values (estimation scale) per parameter; replaces the stepwise search for that parameter. |
threshold |
dOFV threshold; 3.84 is the 95% point of a chi-squared distribution with one degree of freedom. |
steps |
Distances from the estimate, explored on each side in order. |
flat_tol |
dOFV below which a profile counts as flat (allows for the convergence noise of repeated estimation). |
structural |
Optional result of |
map |
Named character vector from fit parameter names to pkident
parameter names, for example |
identifiable: dOFV exceeds threshold on both sides. An approximate
profile-likelihood confidence interval is reported (interpolated on the
signed-root scale; use profile(fit, method = "llp") from nlmixr2extra
for a precise interval).
non_identifiable: dOFV exceeds the threshold on one side only (the
open direction is reported), or on neither side. A profile that stays
within flat_tol of its minimum is reported as flat, which suggests
structural non-identifiability; otherwise it is reported as shallow.
unresolved: a profile fit failed.
Statements are limited to the explored range, which is reported.
Every profile fit is an upper bound of the profile likelihood: a failed or
stuck re-estimation can only overstate dOFV. Each point more than
flat_tol above the original fit, or above the lowest OFV found, is
therefore re-estimated from the estimates of a neighbouring profile point
(a warm start), and the lower value is kept. If profiling
finds a lower OFV than the original fit, dOFV is measured from that lower
value and the drop is reported (ofv_drop): the original fit was not at
its minimum, which is common along non-identifiable directions.
By default, each side is explored by stepping away from the estimate by
steps on the estimation scale, stopping once dOFV exceeds the threshold.
The default steps suit log-transformed parameters (cl <- exp(tcl + ...));
for other parameters give grid.
If a structural_identifiability() result is given with map, each
profile is placed next to the structural decision, which distinguishes
"structurally identifiable but not determined by these data" from
structural non-identifiability.
An object of class pkident_profiles. Use as_table() with
"parameters" or "profiles".
## Not run: fit <- nlmixr2(one_compartment, data, est = "focei") classify_profiles(fit, which = c("tcl", "tv")) ## End(Not run)## Not run: fit <- nlmixr2(one_compartment, data, est = "focei") classify_profiles(fit, which = c("tcl", "tv")) ## End(Not run)
For each candidate design supplied by the user (for example the current design, the same design with an added measured output, with added sampling times, or with an added dose level), reports the structural decision for the outputs it observes and the expected relative standard errors from the Fisher information summed over its experiments. No design is optimised; only the supplied candidates are compared.
compare_designs( m, candidates, values, error = residual_error(prop = 0.1), inputs = list(), structural = TRUE, method = c("both", "lie", "sensitivity"), rse_limit = 30, seed = 1L, rtol = 1e-10, atol = 1e-12 )compare_designs( m, candidates, values, error = residual_error(prop = 0.1), inputs = list(), structural = TRUE, method = c("both", "lie", "sensitivity"), rse_limit = 30, seed = 1L, rtol = 1e-10, atol = 1e-12 )
m |
A |
candidates |
A named list of candidates; each is an |
values |
Named values of all parameters and known constants (the design point). |
error |
A |
inputs |
Known input signals shared by all experiments. |
structural |
If |
method |
Method passed to |
rse_limit |
RSE (%) above which a parameter is |
seed |
Seed for the structural analysis. |
rtol, atol
|
Solver tolerances. |
The structural decision depends only on which outputs are observed (it assumes continuous, noise-free observation after the doses of the model definition). Added sampling times and dose levels therefore change only the expected RSEs.
An object of class pkident_designs. Use as_table() with
"summary" or "parameters".
m <- pkpd_model( "d/dt(central) = -CL/V*central cp = central/V", outputs = "cp", doses = bolus_dose("central", "DOSE"), known = "DOSE" ) compare_designs( m, candidates = list( early = experiment(c(0.25, 0.5, 1, 2), n = 10), extended = experiment(c(0.25, 0.5, 1, 2, 8, 24), n = 10) ), values = c(CL = 2, V = 20, DOSE = 100), error = residual_error(add = 0.05, prop = 0.1), structural = FALSE )m <- pkpd_model( "d/dt(central) = -CL/V*central cp = central/V", outputs = "cp", doses = bolus_dose("central", "DOSE"), known = "DOSE" ) compare_designs( m, candidates = list( early = experiment(c(0.25, 0.5, 1, 2), n = 10), extended = experiment(c(0.25, 0.5, 1, 2, 8, 24), n = 10) ), values = c(CL = 2, V = 20, DOSE = 100), error = residual_error(add = 0.05, prop = 0.1), structural = FALSE )
A group of individuals with the same dosing and sampling.
experiment(times, events = NULL, values = NULL, n = 1, inputs = NULL)experiment(times, events = NULL, values = NULL, n = 1, inputs = NULL)
times |
Sampling times: a numeric vector used for every output, or a named list with one vector per observed output. Outputs not named are not observed in this experiment. |
events |
Optional dosing event table (see |
values |
Optional named values that override the design values for
this experiment, typically known constants such as a dose |
n |
Number of individuals. |
inputs |
Optional known input signals for this experiment (see
|
An object of class pkident_experiment.
experiment(c(0.5, 1, 2, 4, 8, 24), values = c(DOSE = 100), n = 12)experiment(c(0.5, 1, 2, 4, 8, 24), values = c(DOSE = 100), n = 12)
Reads an ODE model through rxode2 and records, as symbolic expressions, the state equations, the observed outputs, the initial conditions, the known doses and input signals, and the unknown parameters. No identifiability decision is made here.
pkpd_model( model, outputs, doses = list(), inputs = character(), known = character(), parameters = NULL, initial = list() )pkpd_model( model, outputs, doses = list(), inputs = character(), known = character(), parameters = NULL, initial = list() )
model |
An rxode2 model: a compiled |
outputs |
Names of the observed quantities: variables assigned in the model or state names. |
doses |
A list of |
inputs |
Names of known input signals (for example a plasma
concentration |
known |
Names of known constants (for example |
parameters |
Names of the unknown parameters to analyse. By default, every symbol that is not a state, an input, a known constant or time. |
initial |
A named list of initial conditions as character expressions,
for example |
An object of class pkident_model.
m <- pkpd_model( "d/dt(depot) = -ka*depot d/dt(central) = ka*depot - CL/V*central cp = central/V", outputs = "cp", doses = list(bolus_dose("depot", "F*DOSE")), known = "DOSE" ) mm <- pkpd_model( "d/dt(depot) = -ka*depot d/dt(central) = ka*depot - CL/V*central cp = central/V", outputs = "cp", doses = list(bolus_dose("depot", "F*DOSE")), known = "DOSE" ) m
Computes the expected Fisher information matrix of the unknown parameters
for one typical individual (or n individuals with the same design), given
the sampling times, the parameter values and the residual error model. The
output sensitivities are obtained by solving the forward sensitivity
equations with rxode2. Because the residual standard deviation may depend
on the prediction (proportional error), the information includes the term
from the variance as well as the term from the mean.
practical_identifiability( m, design, values, error = residual_error(prop = 0.1), inputs = list(), n = 1, events = NULL, rse_limit = 30, rtol = 1e-10, atol = 1e-12 )practical_identifiability( m, design, values, error = residual_error(prop = 0.1), inputs = list(), n = 1, events = NULL, rse_limit = 30, rtol = 1e-10, atol = 1e-12 )
m |
A |
design |
Sampling times: a numeric vector used for every output, or a named list with one vector per output. |
values |
Named numeric vector with a value for every parameter and every known constant of the model. |
error |
A |
inputs |
For models with known input signals, a named list of
character expressions in time |
n |
Number of individuals with the same design (the information is
multiplied by |
events |
Optional dosing event table (a data frame with columns
|
rse_limit |
RSE (%) above which a parameter is |
rtol, atol
|
Solver tolerances. |
The result reports, for each parameter, the expected standard error and relative standard error (RSE, %), the correlation matrix, and the eigen-decomposition of the information matrix on the log-parameter scale: the weakest directions show which combinations of parameters the design determines poorly, and the condition number summarises how poorly.
Statuses:
identifiable: the RSE is finite and at most rse_limit.
non_identifiable: the information matrix is singular in a direction
involving the parameter; the design cannot determine it (structurally or
because of too few samples).
unresolved: the RSE is finite but above rse_limit. The expected
information is a local, linear approximation that is unreliable when
uncertainty is this large; examine likelihood profiles instead.
The default limit of 30% comes from the validation by repeated estimation
on simulated data (validation/fim-simulation.R): with every RSE below
about 15%, expected and observed RSEs agreed within 10%; with RSEs of
30-40%, estimates had skewed tails and the expected RSEs underestimated the
spread, including that of a parameter whose own expected RSE was small.
When any parameter is unresolved, the printed result therefore warns that
the RSEs of all parameters are approximate.
Doses are those in the model (bolus doses at time zero and constant-rate
infusions), whose amounts and rates are known constants supplied in
values, or those of an event table given in events.
An object of class pkident_practical. Use as_table() with
"parameters", "directions" or "correlation".
m <- reference_model("A3") p <- practical_identifiability( m, design = c(0.5, 1, 2, 4, 8, 12, 24), values = c(ka = 1, CL = 2, V = 20, DOSE = 100), error = residual_error(add = 0.05, prop = 0.1) ) pm <- reference_model("A3") p <- practical_identifiability( m, design = c(0.5, 1, 2, 4, 8, 12, 24), values = c(ka = 1, CL = 2, V = 20, DOSE = 100), error = residual_error(add = 0.05, prop = 0.1) ) p
The acceptance models used to validate pkident. Each has an expected result fixed from a published table or an analytical derivation before any identifiability code was written (see the package's validation record).
reference_models() reference_model(id)reference_models() reference_model(id)
id |
Model identifier, for example |
reference_models() lists them; reference_model() builds one as a
pkpd_model() with the expected result attached as attribute expected.
reference_models() returns a data frame; reference_model()
returns a pkident_model.
Janzén DLI, et al. Parameter identifiability of fundamental pharmacodynamic models. Front Physiol. 2016;7:590. doi:10.3389/fphys.2016.00590
Cheung SYA, Yates JWT, Aarons L. The design and analysis of parallel experiments to produce structurally identifiable models. J Pharmacokinet Pharmacodyn. 2013;40:93-100. doi:10.1007/s10928-012-9291-z
reference_models() reference_model("A2")reference_models() reference_model("A2")
Measurement error of an output. With add and prop:
sd = sqrt(add^2 + (prop * f)^2), where f is the model prediction (the
combined2 form of nlmixr2); use add alone for additive error and prop
alone for proportional error. With exp: exponential error, that is
additive error of standard deviation exp on the log scale (data fitted as
log(y)). Exponential error is not combined with the others.
residual_error(add = 0, prop = 0, exp = 0)residual_error(add = 0, prop = 0, exp = 0)
add |
Additive standard deviation (in the units of the output). |
prop |
Proportional standard deviation (for example 0.1 for 10%). |
exp |
Standard deviation on the log scale (exponential error). |
Proportional and exponential errors differ in the Fisher information: with proportional error the variance depends on the parameters and carries information of its own; with exponential error it does not.
An object of class pkident_residual.
residual_error(prop = 0.1) residual_error(add = 0.05, prop = 0.1) residual_error(exp = 0.2)residual_error(prop = 0.1) residual_error(add = 0.05, prop = 0.1) residual_error(exp = 0.2)
Decides local structural identifiability of the unknown parameters of a
pkpd_model() with two independent methods.
structural_identifiability( m, method = c("both", "lie", "sensitivity"), points = 5L, seed = 1L, max_order = NULL, plateau = 3L, tol = 1e-07, max_seconds = 300, max_exponent = 2L, max_support = 4L, times = NULL, sens_tol = 1e-07, rtol = 1e-10, atol = 1e-12 )structural_identifiability( m, method = c("both", "lie", "sensitivity"), points = 5L, seed = 1L, max_order = NULL, plateau = 3L, tol = 1e-07, max_seconds = 300, max_exponent = 2L, max_support = 4L, times = NULL, sens_tol = 1e-07, rtol = 1e-10, atol = 1e-12 )
m |
A |
method |
|
points |
Number of random parameter points. |
seed |
Random seed for the points (the global random state is restored). |
max_order |
Highest derivative order of the Lie method. By default the number of parameters plus the number of states plus two, capped at 14. |
plateau |
Number of consecutive orders without a rank increase needed to conclude rank deficiency (Lie method). |
tol |
Relative singular value below which a direction counts as null (Lie method). |
max_seconds |
Time limit of the Lie method; beyond it the result is
|
max_exponent, max_support
|
Search limits for monomial combinations. |
times |
Time grid of the sensitivity method. By default 0 and 150 log-spaced times from 0.001 to 200, which covers the rate constants implied by the random parameter points (between 0.2 and 5). |
sens_tol |
Relative singular value below which a direction counts as null (sensitivity method). |
rtol, atol
|
Solver tolerances of the sensitivity method. |
Lie method (primary). The outputs are differentiated along the model (Lie derivatives), with known input signals contributing their own time derivatives, and evaluated at the known initial state at time zero (the Taylor-series approach). The parameters are locally identifiable if the Jacobian of these output derivatives with respect to the parameters has full rank. Using the known initial state matters for pharmacokinetic models: a bolus dose fixes the initial amount, which is often what makes a volume identifiable. Methods that evaluate the rank at generic initial states ignore this information.
Sensitivity method (numerical check). The forward sensitivity equations are generated symbolically and solved with rxode2 at the same parameter points; known input signals are replaced by a smooth random signal. The rank of the noise-free output sensitivity matrix on a dense time grid is compared with the Lie rank, and the null spaces of the two methods are compared at every point.
With method = "both" (the default), a decision is made only if the two
methods agree at every point; otherwise the result is unresolved. Ranks
are evaluated at several random parameter points on a log scale, and a
decision is made only if all points agree.
Rank-deficient parameters are reported with the directions along which they
can change together without changing the outputs. Where these directions
correspond to products of powers of parameters (for example V/F), the
identifiable combinations are reported.
Decisions are local: a parameter reported as identifiable may still have a finite number of alternative values elsewhere in parameter space (for example the flip-flop of absorption and elimination rates).
An object of class pkident_structural. Use as_table() to extract
the "parameters", "combinations", "rank" (Lie method) and
"agreement" (comparison of the methods at each point) tables.
r <- structural_identifiability(reference_model("A2"), method = "lie") r as_table(r, "combinations")r <- structural_identifiability(reference_model("A2"), method = "lie") r as_table(r, "combinations")