Sparse NCA Calculations
Sparse noncompartmental analysis (NCA) is performed when multiple individuals contribute to a single concentration-time profile due to the fact that there are only one or a subset of the full profile samples taken per animal. A typical example is when three mice have PK drawn per time point, but no animals have more than one sample drawn. Another typical example is when animals may have two or three samples during an interval, but no animal has the full profile.
Sparse NCA Setup
Sparse NCA is setup the same way as normal, dense PK sampling is
setup with PKNCA. The only difference is that you give the
sparse option to PKNCAconc(); the parameters
are requested by their usual names.
Two things happen behind that flag. For the AUC and AUMC parameters
with a sparse estimator, PKNCA calculates them from the pooled samples
of all the animals in a group with the Bailer point estimate and the
Nedelman-Jia/Holder standard error, and reports the standard error and
degrees of freedom alongside the estimate with the suffixes
_se and _df (for example,
auclast_se and auclast_df). Those parameters
are auclast, aucall, aucinf.obs,
aumclast, aumcall, and
aumcinf.obs, and after an IV bolus aucivlast,
aucivall, aucivinf.obs,
aumcivlast, aumcivall, and
aumcivinf.obs (see
vignette("v24-sparse-auc-to-infinity") for the AUC to
infinity and the IV bolus
).
Every other parameter is calculated from the arithmetic-mean profile of
the animals in the group.
As for any AUC in PKNCA, the sparse AUCs need a concentration at the
start of the interval, measured or imputed. Animals are rarely sampled
at the dose in sacrificial designs, so after an extravascular dose
impute zero with impute = "start_conc0"; the imputed zero
is known, so it adds nothing to the variance.
The example below uses data extracted from Holder D. J., Hsuan F., Dixit R. and Soper K. (1999). A method for estimating and testing area under the curve in serial sacrifice, batch, and complete data designs. Journal of Biopharmaceutical Statistics, 9(3):451-464.
# Setup the data
d_sparse <-
data.frame(
id = c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L, 4L, 5L, 6L, 4L, 5L, 6L, 7L, 8L, 9L, 7L, 8L, 9L),
conc = c(0, 0, 0, 1.75, 2.2, 1.58, 4.63, 2.99, 1.52, 3.03, 1.98, 2.22, 3.34, 1.3, 1.22, 3.54, 2.84, 2.55, 0.3, 0.0421, 0.231),
time = c(0, 0, 0, 1, 1, 1, 6, 6, 6, 2, 2, 2, 10, 10, 10, 4, 4, 4, 24, 24, 24),
dose = c(100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100)
)Look at your data. (This is not technically a required step, but it’s good practice.)
library(ggplot2)
ggplot(d_sparse, aes(x=time, y=conc, group=id)) +
geom_point() +
geom_line() +
scale_x_continuous(breaks=seq(0, 24, by=6))
Data Setup Note
Sparse NCA requires that subject numbers (or animal numbers) are given, even if each subject only contributes a single sample. The reason for this requirement is that which subject contributes to which time point changes the standard error calculation. If all individuals contribute a single sample, a simple way to handle this is by setting a column with sequential numbers and giving that as the subject identifier:
d_sparse$id <- 1:nrow(d_sparse)How Subjects Are Grouped for Sparse Calculations
With dense (normal) PK, every subject has a full concentration-time profile, so NCA parameters are calculated one subject at a time. Sparse parameters are different: they are calculated from the pooled samples of every subject that belongs to the same group. Knowing what defines a “group” is therefore important.
The groups are taken from the grouping variables on the right of the
| in the PKNCAconc() formula, with the
subject column removed. Every subject that shares the same
combination of the remaining (non-subject) grouping variables
contributes to a single pooled sparse concentration-time profile.
In the simple example above, the formula is
conc~time|id. Here id is the subject, and
removing it leaves no other grouping variables, so all of the data form
a single sparse group.
The behavior is easier to see with more grouping variables. Suppose the concentration and dose objects are created with the formulas below (illustrative code; not run here):
o_conc_sparse <- PKNCAconc(d_conc, conc~time|matrix+drug+usubjid/analyte, sparse=TRUE)
o_dose_sparse <- PKNCAdose(d_dose, dose~time|drug+usubjid)usubjid is the subject because, by default, the subject
is the last grouping variable before any / (or the last
grouping variable when there is no /). After dropping the
subject, the grouping variables that remain are matrix,
drug, and analyte. Sparse parameters are
therefore calculated by combining all subjects within each unique
combination of matrix, drug, and
analyte.
In other words, with that formula PKNCA does not
keep each usubjid separate, and it does
not group by matrix, drug, or
analyte alone. It pools subjects using the full set of
non-subject grouping variables together (matrix +
drug + analyte).
Because subjects are pooled within a group, all subjects in a group must share the same dosing. If subjects in the same group have different dosing information (for example, different dose amounts or dose times), PKNCA stops with an error identifying the inconsistent group.
Calculate!
Setup PKNCA for calculations and then calculate!
##
## Attaching package: 'PKNCA'
## The following object is masked from 'package:stats':
##
## filter
o_conc_sparse <- PKNCAconc(d_sparse, conc~time|id, sparse=TRUE)
d_intervals <-
data.frame(
start=0,
end=24,
auclast=TRUE,
auclast_se=TRUE,
auclast_df=TRUE,
aucinf.obs=TRUE,
cmax=TRUE
)
o_data_sparse <- PKNCAdata(o_conc_sparse, intervals=d_intervals)
o_nca <- pk.nca(o_data_sparse)## The sparse estimators use the linear trapezoidal rule, so the auc.method option
## ("lin up/log down") does not apply to: auclast, aucinf.obs
## Warning: Too few points for half-life calculation (min.hl.points=3 with only 2
## points)
pknca_interval_table() builds the same kind of
specification from a description of the study, and its
sparse_single_dose preset is the single-dose set with a
zero imputed at the start (start_conc0), the one imputation
the sparse estimators accept:
pknca_interval_table(0, 24, preset="sparse_single_dose")Results
As with any other PKNCA result, the data are available through the
summary() function:
summary(o_nca)## start end auclast auclast_df cmax aucinf.obs
## 0 24 39.5 [7.31] 2.75 3.05 NC
##
## Caption: auclast, aucinf.obs: estimate and standard error; auclast_df: arithmetic mean and standard deviation; cmax: geometric mean and geometric coefficient of variation; NC: not calculated
In the summary, the sparse auclast is the estimate with
its standard error in brackets (from the auclast_se
result), as the caption says; auclast_se has no column of
its own.
or individual results are available through the
as.data.frame() function:
as.data.frame(o_nca)## # A tibble: 20 × 6
## start end PPTESTCD PPORRES PPANMETH exclude
## <dbl> <dbl> <chr> <dbl> <chr> <chr>
## 1 0 24 cmax 3.05 "" NA
## 2 0 24 tmax 6 "" NA
## 3 0 24 tlast 24 "" NA
## 4 0 24 clast.obs 0.191 "" NA
## 5 0 24 lambda.z NA "" Too fe…
## 6 0 24 r.squared NA "" Too fe…
## 7 0 24 adj.r.squared NA "" Too fe…
## 8 0 24 lambda.z.corrxy NA "" Too fe…
## 9 0 24 lambda.z.time.first NA "" Too fe…
## 10 0 24 lambda.z.time.last NA "" Too fe…
## 11 0 24 lambda.z.n.points NA "" Too fe…
## 12 0 24 clast.pred NA "" Too fe…
## 13 0 24 half.life NA "" Too fe…
## 14 0 24 span.ratio NA "" Too fe…
## 15 0 24 auclast 39.5 "AUC: linear. Sparse: arithm… NA
## 16 0 24 auclast_se 7.31 "AUC: linear. Sparse: arithm… NA
## 17 0 24 auclast_df 2.75 "AUC: linear. Sparse: arithm… NA
## 18 0 24 aucinf.obs NA "AUC: linear. Sparse: arithm… Too fe…
## 19 0 24 aucinf.obs_se NA "AUC: linear. Sparse: arithm… Too fe…
## 20 0 24 aucinf.obs_df NA "AUC: linear. Sparse: arithm… Too fe…
auclast_se and auclast_df are reported
whether or not they were requested, because the sparse estimator returns
all three together. They are only calculable from sparse data:
requesting one for a dense PKNCAconc is an error.
Sparse AUC, AUMC, and Derived Parameters
Because the sparse estimate is stored under the standard parameter
name, every parameter derived from an AUC is available for sparse data
with no extra work. cl.last divides the dose by
auclast, mrt.last divides
aumclast by auclast, and so on down the usual
derived graph.
d_dose <- data.frame(id=unique(d_sparse$id), dose=100, time=0)
o_dose <- PKNCAdose(d_dose, dose~time|id, route="intravascular")
d_intervals_derived <-
data.frame(
start=0,
end=24,
auclast=TRUE,
aumclast=TRUE,
mrt.last=TRUE,
cl.last=TRUE,
kel.last=TRUE,
vss.last=TRUE,
vz.last=TRUE
)
o_data_derived <- PKNCAdata(o_conc_sparse, o_dose, intervals=d_intervals_derived)
o_nca_derived <- pk.nca(o_data_derived)## The sparse estimators use the linear trapezoidal rule, so the auc.method option
## ("lin up/log down") does not apply to: auclast, aumclast
## Warning: Too few points for half-life calculation (min.hl.points=3 with only 2
## points)
as.data.frame(o_nca_derived)## # A tibble: 23 × 6
## start end PPTESTCD PPORRES PPANMETH exclude
## <dbl> <dbl> <chr> <dbl> <chr> <chr>
## 1 0 24 tmax 6 "" NA
## 2 0 24 tlast 24 "" NA
## 3 0 24 cl.last 2.53 "" NA
## 4 0 24 mrt.last 7.49 "" NA
## 5 0 24 vss.last 19.0 "" NA
## 6 0 24 lambda.z NA "" Too few points for half-lif…
## 7 0 24 r.squared NA "" Too few points for half-lif…
## 8 0 24 adj.r.squared NA "" Too few points for half-lif…
## 9 0 24 lambda.z.corrxy NA "" Too few points for half-lif…
## 10 0 24 lambda.z.time.first NA "" Too few points for half-lif…
## # ℹ 13 more rows
Note what vz.last does here. It is the clearance divided
by
,
the terminal rate constant fitted on the mean profile, which is the
standard toxicokinetic approach. These data have only two time points
after the mean profile’s peak, which is too few for a terminal fit, so
and therefore vz.last are NA. That is the
honest answer: the retired vz.sparse.last divided the
clearance by 1/MRT instead, which always produced a number
but made Vz numerically identical to Vss.
kel.last, like the rest of PKNCA’s kel
family, is 1/MRT, so it is unchanged from the retired
kel.sparse.last. Ask for lambda.z when you
want the terminal rate constant itself.
Bootstrap
The sparse standard errors above come from the variance of the means
at each time. A nonparametric bootstrap (Shen and Machado 2017) is an
alternative that applies to every parameter, including Cmax, the
half-life, and ratios between groups: sparse_bootstrap()
resamples the animals with replacement within each set of sampling times
(each time with serial sacrifice, each batch in a batch design, keeping
an animal’s samples together), and every replicate is calculated just as
the original data are. It stores the random seed so the replicates can
be reproduced, and the default of 200 replicates is enough for the mean
and standard deviation (Takemoto et al. 2006).
o_conc_boot <- sparse_bootstrap(o_conc_sparse, seed=20261007)
o_data_boot <-
PKNCAdata(
o_conc_boot,
intervals=data.frame(start=0, end=24, auclast=TRUE, cmax=TRUE, tmax=TRUE),
# Many replicate groups would otherwise show a progress bar
options=list(progress=FALSE)
)
o_nca_boot <- pk.nca(o_data_boot)## The sparse estimators use the linear trapezoidal rule, so the auc.method option
## ("lin up/log down") does not apply to: auclast
summary(o_nca_boot)## start end N auclast cmax tmax
## 0 24 9 39.2 [15.4] 3.29 [13.0] 6.00 [2.00, 10.0]
##
## Caption: auclast, cmax: geometric mean and geometric coefficient of variation; tmax: median and range; N: number of animals in the study; summarized over 200 bootstrap replicates (seed 20261007)
The summary treats the replicates the way the summary of dense data
treats subjects, with each parameter’s usual summary statistics;
N is the number of animals in the study, and the caption
gives the number of replicates. The "original" replicate
holds the data as given, so as.data.frame(o_nca_boot) has
the estimates as well as every replicate. The standard deviation of the
replicates is the standard error of the estimate: with only three
animals at each time, it is smaller for auclast than the
Nedelman-Jia/Holder standard error (auclast_se above),
because resampling three values understates their variance by a factor
of 2/3; vignette("v24-sparse-auc-to-infinity") compares the
methods.
To compare groups, such as a test and a reference formulation in a
parallel design, be_assess() takes the bootstrap results
like any other: the ratio of the estimates is the point estimate, and
its confidence interval is the percentile interval of the replicate
ratios (90% by default), judged against the acceptance limits. For a
crossover, where each animal receives every treatment, give the
treatment column as paired to
sparse_bootstrap() so that each replicate draws the same
animals for every treatment.
d_sparse_formulations <-
rbind(
data.frame(d_sparse, formulation="reference"),
data.frame(
id=d_sparse$id + 100, conc=d_sparse$conc*c(0.9, 1.05, 1.1), time=d_sparse$time,
dose=d_sparse$dose, formulation="test"
)
)
o_conc_formulations <-
PKNCAconc(
d_sparse_formulations, conc~time|formulation+id, sparse=TRUE,
concu="ng/mL", timeu="hr"
)
o_nca_formulations <-
pk.nca(PKNCAdata(
sparse_bootstrap(o_conc_formulations, seed=20261008),
intervals=data.frame(start=0, end=24, auclast=TRUE, cmax=TRUE),
options=list(progress=FALSE)
))## The sparse estimators use the linear trapezoidal rule, so the auc.method option
## ("lin up/log down") does not apply to: auclast
be_assess(
o_nca_formulations, reference_col="formulation", reference_value="reference",
endpoints=c("auclast", "cmax")
)## Bioequivalence assessment: ABE (model_type bootstrap, 90% CI)
## Design: parallel
##
## endpoint test n design units gm_reference gm_reference_lower
## auclast test 18 parallel hr*ng/mL 39.47 30.51
## cmax test 18 parallel ng/mL 3.05 2.65
## gm_reference_upper gm_test gm_test_lower gm_test_upper gmr_percent ci_lower
## 48.68 38.92 31.99 46.63 98.62 72.97
## 4.08 2.99 2.92 3.82 98.23 73.29
## ci_upper cvwr_percent cvwt_percent swr limit_lower limit_upper criterion
## 133.54 NA NA NA 80 125 NA
## 126.14 NA NA NA 80 125 NA
## regulator model_type pass
## ABE bootstrap FALSE
## ABE bootstrap FALSE
##
## Caption: ABE bioequivalence assessment (90% CI). The estimates and their ratio come from the sparse data of each treatment; each 90% CI is the percentile interval of 200 replicates of a stratified nonparametric bootstrap (Shen and Machado 2017). Bioequivalence requires the confidence interval within 80.00-125.00%.
The bootstrap methods are from Shen M. and Machado S. G. (2017). Bioequivalence evaluation of sparse sampling pharmacokinetics data using bootstrap resampling method. Journal of Biopharmaceutical Statistics, 27(2):257-264, and Takemoto S., Yamaoka K., Nishikawa M. and Takakura Y. (2006). Histogram analysis of pharmacokinetic parameters by bootstrap resampling from one-point sampling data in animal experiments. Drug Metabolism and Pharmacokinetics, 21(6):458-464.
Deprecated Parameter Names
Sparse calculations were originally requested through a parallel set of names. All eleven still calculate and still give the values they always gave, but each now warns and will be an error in the next minor release of PKNCA:
| Deprecated | Use instead |
|---|---|
sparse_auclast |
auclast |
sparse_auc_se |
auclast_se |
sparse_auc_df |
auclast_df |
sparse_aumclast |
aumclast |
sparse_aumc_se |
aumclast_se |
sparse_aumc_df |
aumclast_df |
cl.sparse.last |
cl.last |
mrt.sparse.last |
mrt.last |
kel.sparse.last |
kel.last |
vss.sparse.last |
vss.last |
vz.sparse.last |
vz.last |
Every replacement but the last gives the same number as the name it
replaces. vz.last is
-based,
as described above, so it differs from vz.sparse.last by
design.
The functions behind the sparse estimators –
pk.calc.sparse_auc(),
pk.calc.sparse_auclast(),
pk.calc.sparse_aumc(),
pk.calc.sparse_aumclast(), as_sparse_pk(),
sparse_mean(), var_sparse_auc(), and
cov_holder() – are not deprecated. They are what the
unified estimators call, and they remain available for calculations
outside pk.nca().
Notes on Sparse Calculation Behavior
Degrees of freedom with multiple samples per subject
The degrees of freedom (auclast_df and
aumclast_df) are the Satterthwaite approximation of
Nedelman and Jia (1998), which accounts for the correlation between
samples from the same subject, so they are calculated whether each
subject contributes one sample (as in a serial sacrifice design) or
several (as in the example data here and in batch designs). They are
NA when a time point has a single subject, because its
variance cannot be estimated.
More than half of the measurements below the limit of quantification
When calculating the mean concentration at a time point (see
sparse_mean()), if strictly more than 50% of the
measurements at that time point are below the limit of quantification
(BLQ), the mean for that time point is set to zero. At exactly 50% BLQ,
the mean is calculated normally (including the BLQ values as zero).
Only the linear trapezoidal method is supported
The sparse variance theory is derived for a fixed-weight linear
combination of the per-time-point means, so the sparse estimators are
defined with the linear trapezoidal method only. Calling
pk.calc.sparse_auc() with any other method is
an error, and within pk.nca() the sparse estimators always
use the linear method: the auc.method option does not apply
to them, and pk.nca() says so once per run when the option
asks for anything else. Parameters that fall back to the mean profile,
such as aucinf.obs, do follow auc.method.