PKNCA Training Sessions"

knitr::opts_chunk$set(echo = FALSE)
requireNamespace("pmxTools")
library(PKNCA)
library(dplyr)
library(ggplot2)
breaks_hours <- function(n=5, Q=c(1, 6, 4, 12, 2, 24, 168), ...) {
  n_default <- n
  Q_default <- Q
  function(x, n = n_default, Q=Q_default) {
    x <- x[is.finite(x)]
    if (length(x) == 0) {
      return(numeric())
    }
    rng <- range(x)
    labeling::extended(rng[1], rng[2], m=n, Q=Q, ...)
  }
}

scale_x_hours <- function(..., breaks=breaks_hours()) {
  ggplot2::scale_x_continuous(..., breaks=breaks)
}

Introduction to PKNCA and Basics of Its Use

Creation of these materials were supported by funding from the Metrum Research Group.

Introduction to PKNCA {.build .smaller}

PKNCA is a tool for calculating noncompartmental analysis (NCA) results for pharmacokinetic (PK) data.

... but, you already knew that or you wouldn't be here.

PKNCA has several foci:

Enjoy! {.build}

I hope that you have a whale of a good time during this training.

(Foreshadowing...)

Some NCA Definitions

Dataset Basics

NCA Data are Not Tidy as a Single Dataset

"Tidy datasets... have a specific structure: each variable is a column, each observation is a row, and each type of observational unit is a table." - Hadley Wickham (https://doi.org/10.18637/jss.v059.i10)

CDISC has NCA tidied, and PKNCA follows that model:

Dataset Basics: Minimum data

PKNCA requires at minimum concentration, time, and what you want to calculate.

conc <-
  datasets::Theoph %>%
  filter(Subject %in% 1)
ggplot(conc, aes(x=Time, y=conc)) +
  geom_line() +
  scale_x_hours()

Dataset Basics: What columns are needed?

Column names are provided by the input to PKNCAconc() and PKNCAdose(); they are not hard-coded.

Columns that can be used include:

Dataset Basics: Example data

In the following slides, abbreviated data from an example study where two treatments ("A" and "B") are administered to two subjects (1 and 2).

Dataset Basics: Example concentration data {.columns-2}

conc_data <-
  withr::with_seed(5, {
    data.frame(
      Subject=rep(1:2, each=6),
      Treatment=rep(c("A", "B", "A", "B"), each=3),
      Time=rep(c(0, 2, 8), 4),
      Conc=rep(c(0, 2, 0.5), 4)*exp(rnorm(n=12, sd=0.05))
    )
  })
pander::pander(conc_data %>% filter(Subject == 1))

pander::pander(conc_data %>% filter(Subject == 2))

Dataset Basics: Example dosing data

dose_data <-
  data.frame(
    Subject=rep(1:2, each=2),
    Treatment=c("A", "B", "A", "B"),
    Time=0,
    Dose=10
  )
pander::pander(dose_data %>% filter(Subject == 1))

pander::pander(dose_data %>% filter(Subject == 2))

Dataset Basics: Example interval data

d_interval_1 <-
  data.frame(
    start=0, end=8,
    cmax=TRUE, tmax=TRUE, auclast=TRUE
  )
pander::pander(d_interval_1)

Groups are not required, if you want the same intervals calculated for each group.

Hands-on: First NCA calculation with PKNCA

library(dplyr)
library(ggplot2)
library(tidyr)
library(purrr)
library(PKNCA)
# Concentration data setup
d_conc <-
  datasets::Theoph %>%
  filter(Subject %in% 1)
o_conc <- PKNCAconc(conc~Time, data=d_conc)
# Setup intervals for calculation
d_intervals <- data.frame(start=0, end=24, cmax=TRUE, tmax=TRUE, auclast=TRUE, aucint.inf.obs=TRUE)
# Combine concentration and dose
o_data <- PKNCAdata(o_conc, intervals=d_intervals)
# Calculate the results (suppressMessages() hides a message that isn't needed now)
o_result <- suppressMessages(pk.nca(o_data))
# summary(o_result)

PKNCA Functions

What functions are the most used?

How do I do a simple calculation? all steps

We will break this down in subsequent slides.

# Concentration data setup
d_conc <-
  datasets::Theoph %>%
  filter(Subject %in% 1)
o_conc <- PKNCAconc(conc~Time, data=d_conc)
# Dose data setup
d_dose <-
  datasets::Theoph %>%
  filter(Subject %in% 1) %>%
  filter(Time == 0)
o_dose <- PKNCAdose(Dose~Time, data=d_dose)
# Combine concentration and dose
o_data <- PKNCAdata(o_conc, o_dose)
# Calculate the results
o_result <- pk.nca(o_data)

How do I do a simple calculation? Concentration data {.smaller}

# Load your dataset as a data.frame
d_conc <-
  datasets::Theoph %>%
  filter(Subject %in% 1)
# Take a look at the data
pander::pander(head(d_conc, 2))
# Define the PKNCAconc object indicating the concentration and time columns, the
# dataset, and any other options.
o_conc <- PKNCAconc(conc~Time, data=d_conc)

How do I do a simple calculation? Dose data {.smaller}

# Load your dataset as a data.frame
d_dose <-
  datasets::Theoph %>%
  filter(Subject %in% 1) %>%
  filter(Time == 0)
# Take a look at the data
pander::pander(d_dose)
# Define the PKNCAdose object indicating the dose amount and time columns, the
# dataset, and any other options.
o_dose <- PKNCAdose(Dose~Time, data=d_dose)

How do I do a simple calculation? Calculate results {.smaller}

# Combine the PKNCAconc and PKNCAdose objects.  You can add interval
# specifications and calculation options here.
o_data <- PKNCAdata(o_conc, o_dose)
# Calculate the results
o_result <- pk.nca(o_data)

How do I do a simple calculation? Get results

To calculate summary statistics, use summary(); to extract all individual-level results, use as.data.frame().

The "caption" attribute of the summary describes how the summary statistics were calculated for each parameter. (Hint: pander::pander() knows how to use that to put the caption on a table in a report.)

The individual results contain the columns for start time, end time, grouping variables (none in this example), parameter names, values, and if the value should be excluded.

How do I do a simple calculation? Get summary results {.smaller}

# Look at summarized results
pander::pander(summary(o_result))

How do I do a simple calculation? Get individual results {.smaller}

# Look at individual results
pander::pander(head(
  as.data.frame(o_result),
  n=3
))

PKNCA datasets

How does PKNCA think about data?

Three types of data are inputs for calculation in PKNCA:

PKNCAconc and PKNCAdose objects can optionally have groups. The groups in a PKNCAdose object must be the same or fewer than the groups in PKNCAconc object (for example, all subjects in a treatment arm may receive the same dose).

What is an "interval" and how is it different than a "group"? {.columns-2 .smaller}

last_dose_time <- 24
dose_interval <- 8
dose_times <- seq(0, last_dose_time-dose_interval, by=dose_interval)
d_conc_superposition <-
  superposition(
    o_conc,
    dose.times=dose_times,
    tau=last_dose_time,
    check.blq=FALSE,
    n.tau=1
  )

A group separates one full concentration-time profile for a subject that you may ever want to consider at the same time. Usually, it groups by study, treatment, analyte, and subject (other groups can be useful depending on the study design).

An interval selects a time range within a group.

One time can be in zero or more intervals, but only zero or one group. Intervals can be adjacent (0-12 and 12-24) or overlap (0-12 and 0-24). In other words, one sample may be used in more than one interval, but one sample will never be used in more than one group.

Legend: The group contains all points on the figure. Shaded regions indicate intervals. Arrows indicate points shared between intervals within the group.

d_intervals <-
  tibble(
    start=dose_times,
    end=dose_times + dose_interval
  ) %>%
  mutate(
    name=sprintf("Interval %g", row_number()),
    height=max(d_conc_superposition$conc)*1.03,
    width=dose_interval,
    x=(start+end)/2,
    y=height/2
  )
d_interval_arrows <-
  d_conc_superposition %>%
  filter(time != 0 & time %in% dose_times) %>%
  mutate(
    name1=sprintf("Interval %g", row_number()),
    name2=sprintf("Interval %g", row_number() + 1),
  )
ggplot(d_conc_superposition, aes(x=time, y=conc)) +
  geom_tile(
    data=d_intervals,
    aes(x=x, y=y, width=width, height=height, colour=name, fill=name),
    alpha=0.2,
    inherit.aes=FALSE,
    show.legend=FALSE
  ) +
  geom_segment(
    data=d_interval_arrows,
    aes(x=time - 0.8, xend=time - 0.1, y=conc-2.1, yend=conc - 0.1, colour=name2),
    arrow=arrow(length=unit(0.1, "inches")),
    inherit.aes=FALSE,
    show.legend=FALSE
  ) +
  geom_segment(
    data=d_interval_arrows,
    aes(x=time + 0.8, xend=time + 0.1, y=conc-2.1, yend=conc - 0.1, colour=name1),
    arrow=arrow(length=unit(0.1, "inches")),
    inherit.aes=FALSE,
    show.legend=FALSE
  ) +
  geom_line() +
  geom_point() +
  scale_x_hours() +
  labs(
    title=sprintf("Dosing Q%gH", dose_interval)
  )

Common data management requirements before sending data to PKNCA {.smaller}

  1. Time must not be missing for PKNCAconc (if given to PKNCAdose, it must not be missing).
  2. Below the limit of quantification (BLQ) concentrations must be set to zero (not NA).
  3. Imputation of time zero is required for AUC calculation.
  4. Especially for actual-time calculations, imputation of the beginning of the interval is usually needed.

Columns must be created for:

Setup your concentration data {.columns-2}

A

Setup your concentration data {.columns-2}

NA

Setup your concentration data {.columns-2}

![](https://apps-afsc.fisheries.noaa.gov/Quarterly/amj2005/images/killerwhales.jpg)
Group: 🗸 a pod of killer whales

Setup your dosing data (if you have it and even if you don't) {.smaller}

Normal dosing data setup: PKNCAdose(dose~time|actarm+usubjid, data=d_dose)

Define your intervals

Intervals have columns for:

Define your intervals: example

PKNCA.options("single.dose.aucs") %>%
  select(c(all_of(c("start", "end")), where(~is.logical(.x) && any(.x)))) %>%
  pander::pander()

Calculations above the hood

Prepare your data for calculation

d_conc <-
  datasets::Theoph %>%
  mutate(
    Treatment=
      case_when(
        Dose <= median(Dose)~"Low dose",
        TRUE~"High dose"
      )
  )
# The study was single-dose
d_dose <-
  d_conc %>%
  select(Treatment, Subject, Dose) %>%
  unique() %>%
  mutate(dose_time=0)

Calculate without dosing data {.build}

o_conc <- PKNCAconc(conc~Time|Treatment+Subject, data=d_conc)
try({
  o_data <- PKNCAdata(o_conc)
  summary(pk.nca(o_data))
})

Whoops! Without dosing, we need intervals.

Calculate without dosing data, try 2

o_conc <- PKNCAconc(conc~Time|Treatment+Subject, data=d_conc)
d_intervals <- data.frame(start=0, end=Inf, cmax=TRUE, tmax=TRUE, half.life=TRUE, aucinf.obs=TRUE)
o_data_manual_intervals <- PKNCAdata(o_conc, intervals=d_intervals)
summary(pk.nca(o_data_manual_intervals))

Dosing data helps with interval setup

o_conc <- PKNCAconc(conc~Time|Treatment+Subject, data=d_conc)
o_dose <- PKNCAdose(Dose~dose_time|Treatment+Subject, data=d_dose)
o_data_auto_intervals <- PKNCAdata(o_conc, o_dose)
o_data_auto_intervals$intervals$aucint.inf.obs <- TRUE
summary(pk.nca(o_data_auto_intervals))

AUC considerations with PKNCA (1/3) {.columns-2}

d_conc <-
  datasets::Theoph %>%
  filter(Subject == 1)
o_conc <- PKNCAconc(conc~Time, data=d_conc)
d_interval_int <- data.frame(start=0, end=Inf, half.life=TRUE)
o_data_int <- PKNCAdata(o_conc, intervals=d_interval_int)
o_nca_int <- suppressMessages(pk.nca(o_data_int))
lambda_z_int <-
  o_nca_int %>%
  as.data.frame() %>%
  filter(PPTESTCD %in% "lambda.z") %>%
  "[["("PPORRES")

d_interval_inf <- data.frame(start=0, end=24, half.life=TRUE)
o_data_inf <- PKNCAdata(o_conc, intervals=d_interval_inf)
o_nca_inf <- suppressMessages(pk.nca(o_data_inf))
lambda_z_inf <-
  o_nca_inf %>%
  as.data.frame() %>%
  filter(PPTESTCD %in% "lambda.z") %>%
  "[["("PPORRES")

tlast <- 
  o_nca_inf %>%
  as.data.frame() %>%
  filter(PPTESTCD %in% "tlast") %>%
  "[["("PPORRES")

d_auc_calcs <-
  d_conc %>%
  bind_rows(
    tibble(Time=seq(12, 60))
  ) %>%
  mutate(
    conc_all_int=
      interp.extrap.conc(
        conc=conc[!is.na(conc)],
        time=Time[!is.na(conc)],
        time.out=Time,
        lambda.z=lambda_z_int
      ),
    conc_all_inf=
      interp.extrap.conc(
        conc=conc[!is.na(conc) & Time <= 24],
        time=Time[!is.na(conc) & Time <= 24],
        time.out=Time,
        lambda.z=lambda_z_inf
      ),
    conc_last=
      case_when(
        Time <= 24~conc,
        TRUE~NA_real_
      ),
    conc_int=
      case_when(
        Time <= 24 & Time >= tlast~conc_all_int,
        TRUE~NA_real_
      ),
    conc_inf=
      case_when(
        Time >= tlast~conc_all_inf,
        TRUE~NA_real_
      )
  ) %>%
  arrange(Time)
auc_figure_time_max <- 36
p_auc_calcs <-
  ggplot(d_auc_calcs, aes(x=Time, y=conc)) +
  # AUCinf (with a work-around for https://github.com/tidyverse/ggplot2/issues/4661)
  geom_area(
    data=d_auc_calcs %>% filter(Time <= auc_figure_time_max),
    aes(y=conc_inf, colour="AUCinf", fill="AUCinf"),
    alpha=0.2,
    na.rm=TRUE
  ) +
  geom_line(
    data=d_auc_calcs,
    aes(y=conc_inf, colour="AUCinf"),
    na.rm=TRUE
  ) +
  # AUCint
  geom_area(
    aes(y=conc_int, colour="AUCint", fill="AUCint"),
    alpha=0.2,
    na.rm=TRUE
  ) +
  # AUClast
  geom_area(
    aes(y=conc_last, colour="AUClast", fill="AUClast"),
    na.rm=TRUE
  ) +
  geom_point(show.legend=FALSE,
    na.rm=TRUE) +
  geom_line(show.legend=FALSE,
    na.rm=TRUE) +
  geom_vline(xintercept=24, linetype="63") +
  scale_x_continuous(breaks=seq(0, auc_figure_time_max, by=6)) +
  coord_cartesian(xlim=c(0, auc_figure_time_max)) +
  labs(
    colour="AUC type",
    fill="AUC type"
  )
p_auc_calcs

The considerations below mainly apply to actual-time data; nominal-time data usually have measurements at the start and end time for the interval.

With an interval start and end of 0 and 24 (and the last measurement time just after 24 hours):

AUC considerations with PKNCA (2/3) {.columns-2}

p_auc_calcs

The considerations below mainly apply to actual-time data; nominal-time data usually have measurements at the start and end time for the interval.

With an interval start and end of 0 and 24 (and the last measurement time just after 24 hours):

AUC considerations with PKNCA (2/3) {.columns-2}

p_auc_calcs

The considerations below mainly apply to actual-time data; nominal-time data usually have measurements at the start and end time for the interval.

With an interval start and end of 0 and 24 (and the last measurement time just after 24 hours):

Hands-on workshop

Steady-state intramuscular administration

The data for the exercise are from a PK study of amikacin in a killer whale and a beluga whale. (DOI: 10.1638/03-078)

(Callback...)

Steady-state intramuscular administration

library(PKNCA)

d_conc <- read.csv("c:/tmp/whale_conc.csv")
d_dose <- read.csv("c:/tmp/whale_dose.csv")
head(d_conc)
head(d_dose)

o_conc <- PKNCAconc(concentration~time|Animal, data=d_conc)
o_dose <- PKNCAdose(dose~time|Animal, data=d_dose)
o_data <- PKNCAdata(o_conc, o_dose)
o_data$intervals
o_nca <- pk.nca(o_data)
summary(o_nca)
summary(o_nca, drop.group=c())
as.data.frame(o_nca)

Day 2 Start

Control your data

Including and excluding data points

Data may be included/excluded in two ways:

For both ways of including/excluding data, it is defined by a column in the input data. The column is either NA or an empty string ("") to indicate "no" or any other text to indicate "yes".

Exclude data points overall {.columns-2}

Use the exclude argument for PKNCAconc() or PKNCAdose().

When you use exclude, this is how you give your data to PKNCA:

d_before_exclude <-
  data.frame(
    time=0:4,
    conc=c(0, 2, 1, 0.5, 0.25),
    not_this=c(NA, "Not this", NA, NA, NA)
  )
o_conc <-
  PKNCAconc(
    data=d_before_exclude,
    conc~time,
    exclude="not_this"
  )

And, this is how PKNCA thinks about it:

pander::pander(
  d_before_exclude %>%
    filter(is.na(not_this))
)

Exclude data points overall

o_conc <- PKNCAconc(data=d_before_exclude, conc~time, exclude="not_this")
Hey babe, did you get my 5 rows of data?
I only saw 4 rows. Are you sure you sent 5?
Yep, definitely 5 check that last slide. 😠
...
New phone. Who dis?

Digression: How is λz automatically calculated? {.smaller}

Note: WinNonlin first requires λz> 0 then selects for adjusted r^2^. Therefore, WinNonlin will occasionally provide a half-life when PKNCA will not, but the fit line is not as good (as measured by r^2^). The selection of filtering order is an intentional feature with PKNCA, and it generally has minimal impact on summary statistics because the quality of the half-life fit is usually low in this scenario.

λz control (manual exclusions and inclusions of data points)

Use the exclude_half.life or include_half.life argument for PKNCAconc(). The two arguments behave very differently in how points are selected for half-life.

exclude_half.life uses the same automatic point selection method of curve stripping (described before), but it excludes individual points from that calculation.

include_half.life uses no automatic point selection method, and only points specifically noted by the analyst are included.

Less-common calculations

Urine calculations

d_urine <-
  data.frame(
    conc=c(1, 2, 3),
    urine_volume=c(200, 100, 300),
    time=c(1, 2, 3)
  )
o_conc <- PKNCAconc(data=d_urine, conc~time, volume="urine_volume")
d_intervals <- data.frame(start=0, end=24, ae=TRUE)
o_data <- PKNCAdata(o_conc, intervals=d_intervals)
o_nca <- suppressMessages(pk.nca(o_data))
summary(o_nca)

Urine calculations: understanding what is happening and potential hiccups

Intervals for urine are treated the same as any other interval type. Specifically, PKNCA does not look outside the start and end of the interval.

Calculations below the hood

PKNCA only calculates what is required, not every possible parameter (1 of 2)

If you don't need a parameter, PKNCA won't calculate it.

For example, if all you need is cmax, all you'll get is cmax.

o_conc <- PKNCAconc(data=data.frame(conc=2^-(1:4), time=0:3), conc~time)
o_data <- PKNCAdata(o_conc, intervals=data.frame(start=0, end=Inf, cmax=TRUE))
o_nca <- suppressMessages(pk.nca(o_data))
as.data.frame(o_nca)

PKNCA only calculates what is required, not every possible parameter (2 of 2) {.columns-2 .smaller}

If you need AUC~0-\infty~, PKNCA will calculate other required parameters behind the scenes.

o_data <-
  PKNCAdata(
    o_conc,
    intervals=
      data.frame(
        start=0, end=Inf,
        aucinf.obs=TRUE
      )
  )
o_nca <- suppressMessages(pk.nca(o_data))

as.data.frame(o_nca)

How to select the correct parameters for calculations (aka, why are there r sum(grepl(x=names(PKNCA.options("single.dose.aucs")), pattern="^auc")) types of AUC in PKNCA?)

CDISC has one set of names, but they are not precise (e.g. AUCINT doesn't tell the interpolation/extrapolation method).

PKNCA tries to be everything to everyone (in terms of parameters calculated), and it simultaneously tries to be precise. That yields many parameters.

See the Selection of Calculation Intervals vignette in the Parameters Available for Calculation in an Interval section for all available parameters.

When are intervals (partly) ignored? {.smaller}

Very few parameters reach outside of the start and end of an interval for additional information about what is being calculated. As of the writing of these training materials (PKNCA version 0.9.5), the only parameters that look outside are the aucint class of parameters.

AUC~int~ may look after the end of the interval to calculate the concentration at end.

Note: Watch out for a dose before the next concentration (e.g. a dose at 24 hours but the prior sample is around 12 and the next is around 25):

d_prep <-
  datasets::Theoph %>%
  filter(Subject == 1) %>%
  mutate(
    conc=
      case_when(
        Time == 0~0,
        TRUE~conc
      )
  )
d_plot <-
  superposition(
    conc=d_prep$conc,
    time=d_prep$Time,
    tau=48,
    n.tau=1,
    dose.times=24*(0:1)
  ) %>%
  # Pretend that we missed the predose sample
  filter(
    !between(time, 23, 24.5)
  )
# Pretend that there was a dose at 24
ggplot(d_plot, aes(x=time, y=conc)) +
  geom_point() + geom_line() +
  geom_vline(xintercept=24) +
  scale_x_hours()

Control your results

Excluding results (Not the best way) {.smaller}

A simple way to exclude a value from results is to convert the results to a data.frame and then drop the rows you don't want:

o_conc <- PKNCAconc(data=data.frame(conc=2^-(1:4), time=0:3), conc~time)
o_data <-
  PKNCAdata(
    o_conc,
    intervals=
      data.frame(
        start=0, end=Inf,
        aucinf.obs=TRUE
      )
  )
o_nca <- suppressMessages(pk.nca(o_data))
as.data.frame(o_nca) %>%
  filter(PPTESTCD != "half.life")

But, parameters derived from half-life remain.

Excluding results (The best way, 1/2) {.columns-2 .smaller}

When you use the exclude() function, parameters that are dependent on an excluded parameter will be excluded.

o_nca_excluded <-
  o_nca %>%
  exclude(FUN=exclude_nca_span.ratio(3))
as.data.frame(o_nca_excluded)

Excluding results (The best way, 2/2) {.smaller}

Now, everything dependent on the half-life is excluded in summaries.

summary(o_nca)
summary(o_nca_excluded)

NCA-related calculations

Superposition {.smaller .columns-2}

Superposition assumes linear kinetics and can convert a single-dose profile to multi-dose.

# Subject 2 is selected for a BLQ time=0 concentration
d_prep <-
  datasets::Theoph %>%
  filter(Subject == 2)
# Superposition to steady-state is the default
d_ss <-
  superposition(
    conc=d_prep$conc,
    time=d_prep$Time,
    tau=24
  )
# Going to steady-state is also an option
# (n.tau=2 means the second dose)
d_second_dose <-
  superposition(
    conc=d_prep$conc,
    time=d_prep$Time,
    tau=24,
    n.tau=2
  )

# Want the profile for the first two doses
# together?
d_first_two <-
  superposition(
    conc=d_prep$conc,
    time=d_prep$Time,
    tau=48, # 48 hours
    n.tau=1, # One tau interval (0 to 48 hours)
    dose.times=c(0, 24)
  )
ggplot(d_ss, aes(x=time, y=conc)) +
  geom_point() + geom_line() +
  scale_y_continuous(limits=c(0, NA))

Time-to-Steady-state calculations {.smaller}

Time-to-steady-state (tss) can be useful as a method to confirm that a subject is at steady-state. PKNCA can calculate tss using trough concentrations either with a monoexponential increase toward steady-state (preferred) or a linear trend back from the final point.

dose_times <- seq(0, 96-1, by=6)
d_multidose <-
  superposition(
    conc=d_prep$conc,
    time=d_prep$Time,
    tau=96, # 48 hours
    n.tau=1, # One tau interval (0 to 48 hours)
    dose.times=dose_times
  )
pk.tss.monoexponential(
  conc=d_multidose$conc, time=d_multidose$time, subject=rep(1, nrow(d_multidose)),
  time.dosing=dose_times, subject.dosing=rep(1, length(dose_times)),
  output="single"
)

Reporting

Graphics are intentionally not part of PKNCA, but there are some tricks that can help...

Generate all individual profiles using the groups that you defined:

o_conc <- PKNCAconc(conc~Time|Subject, data=datasets::Theoph)
d_plot <-
  grouped_df(data=datasets::Theoph, vars=names(getGroups(o_conc))) %>%
  nest() %>%
  mutate(
    figure=
      lapply(
        pmap(
          .l=list(data=data),
          .f=ggplot,
          aes(x=Time, y=conc)
        ),
        FUN="+",
        geom_line()
      )
  )
# d_plot$figure

Best practices for Data -> PKNCA -> knitr

Make summary tables using the summary() function on the NCA results, and use pander::pander() to make a pretty table with captions.

pander::pander(summary(o_nca))

Make an NCA data listing using the as.data.frame() function on the NCA results.

pander::pander(as.data.frame(o_nca))

Limitations

Units (especially clearance)

PKNCA does not (yet) support units. The most common place where that becomes an issue is with clearance which ends up having unusual units like "mg/(hr*ng/mL)" (with units of mg for dosing, hr for time, and ng/mL for concentration).

Data imputation

Some data points are required for inputs such as:

IV bolus AUC (need to add C0)

PKNCA does not have an IV-aware AUC calculation (yet). So, C0 must be separately imputed.

Combined, multi-subject data (e.g. sparse animal sampling)

Averaging the concentration at a time point will allow for the mean sparse sampling result. But general sparse sampling methods are not (yet) implemented.

Secondary parameters (e.g. bioavailability and renal clearance)

PKNCA does not (yet) have the ability to calculate secondary PK parameters that require looking at more than one group/interval at a time. (If you have good ideas for how to define the intervals for that, please let me know.)

Hands-on

Single- and Multiple-dose, single analyte: Setup the underlying datasets {.smaller}

d_conc <-
  datasets::Theoph %>%
  rename(time=Time) %>%
  mutate(
    Subject=as.character(Subject)
  )
d_multidose <-
  PKNCAconc(conc~time|Subject, data=d_conc) %>%
  superposition(tau=24, check.blq=FALSE)
d_singledose_single_analyte <-
  d_conc %>%
  mutate(
    Study_Part="Single"
  )
d_multidose_single_analyte <-
  d_conc %>%
  mutate(Day=1) %>%
  bind_rows(
    d_multidose %>% mutate(time=time + 120, Day=6)
  ) %>%
  mutate(
    Study_Part="Multiple"
  )

Single- and Multiple-dose, single analyte: Setup the concentration and dose datasets {.smaller}

d_single_multi_conc <- bind_rows(d_singledose_single_analyte, d_multidose_single_analyte)
d_single_multi_dose <-
  d_single_multi_conc %>%
  filter(
    (Study_Part %in% "Single" & time == 0) |
      (Study_Part %in% "Multiple" & (time %% 24) == 0)
  )

Single- and Multiple-dose, single analyte: Perform basic analysis {.smaller}

o_conc <- PKNCAconc(data=d_single_multi_conc, conc~time|Study_Part+Subject)
o_dose <- PKNCAdose(data=d_single_multi_dose, Dose~time|Study_Part+Subject)
o_data <- PKNCAdata(o_conc, o_dose)
o_data$intervals %>% select(-Subject) %>% unique() %>% as.data.frame()
o_nca <- pk.nca(o_data)

Single- and Multiple-dose, single analyte: Use intervals for fewer subjects {.smaller}

d_intervals <-
  data.frame(
    start=0,
    end=24,
    Subject=c("1", "2"),
    Study_Part="Single",
    aucinf.obs=TRUE
  )
o_data <- PKNCAdata(o_conc, o_dose, intervals=d_intervals)
o_nca <- pk.nca(o_data)
summary(o_nca)

Single- and Multiple-dose, single analyte: Use custom intervals per subjects {.smaller}

# Find the time closest to 12 hours
d_intervals_prep <-
  d_single_multi_conc %>%
  filter(Study_Part == "Single") %>%
  mutate(
    time_deviation=abs(time-12)
  ) %>%
  group_by(Subject, Study_Part) %>%
  filter(time %in% time[time_deviation == min(time_deviation)])
d_intervals <-
  d_intervals_prep %>%
  select(Study_Part, Subject, end=time) %>%
  mutate(
    start=0,
    aucinf.obs=TRUE
  )
o_data <- PKNCAdata(o_conc, o_dose, intervals=d_intervals)

o_nca <- pk.nca(o_data)
summary(o_nca, drop.group=c("Subject", "end"))

Single- and Multiple-dose, parent and metabolite

d_single_multi_conc_multi_analyte <-
  bind_rows(
    d_single_multi_conc %>% mutate(Analyte="Parent"),
    d_single_multi_conc %>%
      mutate(
        Analyte="Metabolite",
        conc=conc/2
      )
  )
o_conc <-
  PKNCAconc(
    data=d_single_multi_conc_multi_analyte,
    conc~time|Study_Part+Subject/Analyte
  )
o_dose <- PKNCAdose(data=d_single_multi_dose, Dose~time|Study_Part+Subject)
o_data <- PKNCAdata(o_conc, o_dose)
o_nca <- pk.nca(o_data)
summary(o_nca)


Try the PKNCA package in your browser

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

PKNCA documentation built on April 30, 2023, 1:08 a.m.