Skip to contents

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 auclast and aumclast, PKNCA uses the sparse estimators – the Bailer point estimate with the Nedelman-Jia/Holder standard error – calculated from the pooled samples of all the animals in a group, and reports the standard error and degrees of freedom alongside the estimate as auclast_se/auclast_df and aumclast_se/aumclast_df. Every other parameter is calculated from the arithmetic-mean profile of the animals in the group.

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
## Warning: Cannot yet calculate sparse degrees of freedom for multiple samples
## per subject
## 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 no imputation (there is no individual profile to impute into):

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_se auclast_df cmax aucinf.obs
##      0  24    39.5       7.31         NC 3.05         NC
## 
## Caption: auclast, cmax, aucinf.obs: geometric mean and geometric coefficient of variation; auclast_se, auclast_df: arithmetic mean and standard deviation; NC: not calculated

or individual results are available through the as.data.frame() function:

## # A tibble: 18 × 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 aucinf.obs           NA     "AUC: lin up/log down"        Too fe…
## 16     0    24 auclast              39.5   "AUC: linear. Sparse: arithm… NA     
## 17     0    24 auclast_se            7.31  "AUC: linear. Sparse: arithm… NA     
## 18     0    24 auclast_df           NA     "AUC: linear. Sparse: arithm… NA

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: Cannot yet calculate sparse degrees of freedom for multiple samples
## per subject
## Warning: Cannot yet calculate sparse degrees of freedom for multiple samples
## per subject
## 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 λz\lambda_z, 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 λz\lambda_z 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.

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 λz\lambda_z-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) can only be calculated when each subject contributes a single sample to the profile (as in a serial sacrifice design). When any subject contributes more than one sample, as in the example data here, PKNCA warns that it “Cannot yet calculate sparse degrees of freedom for multiple samples per subject”, and the degrees of freedom are NA. That warning is the source of the warnings in the results above. The point estimates and standard errors are still calculated.

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.