pharmaverse examples
  1. TLG
  2. Pharmacokinetic
  • Introduction

  • SDTM
    • SDTM Examples
  • ADaM
    • ADSL
    • ADPC
    • ADPPK
    • ADRS
    • ADTTE
    • ADVS
    • ADAE
  • TLG
    • Demographic Table
    • Adverse Events
    • Pharmacokinetic
  • Interactive
    • teal applications
  • Logs
    • The Difference Between logr, logrx, and whirl
  • eSubmission
    • eSubmission

  • Session Info
  • Pharmaverse Home
  1. TLG
  2. Pharmacokinetic

Pharmacokinetic

Introduction

This guide will show you how pharmaverse packages, along with some from tidyverse, can be used to create pharmacokinetic (PK) tables, listings and graphs, using the {pharmaverseadam} ADSL and ADPC data as an input.

The packages used with a brief description of their purpose are as follows:

  • {rtables}: designed to create and display complex tables with R.
  • {tern}: contains analysis functions to create tables and graphs used for clinical trial reporting.
  • {rlistings}: contains framework for creating listings for clinical reporting.

See catalog for PK TLGs here PK TLG catalog

See the {admiral} Guide for creating a PK NCA ADaM for more information about the structure of ADPC. See also ADPC under the ADaM section on the left panel.

Data preprocessing

Here we set up the data for the table, graph and listing. We will read ADPC and ADSL from {pharmaverseadam}. We use tern::df_explicit_na() to set missing values as categorical. In ADPC we will keep only concentration records (dropping dosing records), and for this example we will only keep plasma concentrations (dropping urine). The ADPC data also includes duplicated records for analysis with DYTPE == "COPY" we will drop these as well (These are removed by selecting ANL02FL == "Y").

library(pharmaverseadam)
library(tern)
library(dplyr)
library(ggplot2)
library(nestcolor)
library(rlistings)

# Read data from pharmaverseadam
adpc <- pharmaverseadam::adpc
adsl <- pharmaverseadam::adsl

# Use tern::df_explicit_na() to end encode missing values as categorical
adsl <- adsl %>%
  df_explicit_na()

adpc <- adpc %>%
  df_explicit_na()

# For ADPC keep only concentration records and treated subjects
# Keep only plasma records for this example
# Remove DTYPE = COPY records with ANL02FL == "Y"
adpc <- adpc %>%
  filter(PARAMCD != "DOSE" & TRT01A != "Placebo" & PARCAT1 == "PLASMA" & ANL02FL == "Y")

PK Table

Now we create the PK table.

# Setting up the data for table
adpc_t <- adpc %>%
  mutate(
    NFRLT = as.factor(NFRLT),
    AVALCAT1 = as.factor(AVALCAT1),
    NOMTPT = as.factor(paste(NFRLT, "/", PCTPT))
  ) %>%
  select(NOMTPT, ACTARM, VISIT, AVAL, PARAM, AVALCAT1)

adpc_t$NOMTPT <- factor(
  adpc_t$NOMTPT,
  levels = levels(adpc_t$NOMTPT)[order(as.numeric(gsub(".*?([0-9\\.]+).*", "\\1", levels(adpc_t$NOMTPT))))]
)

# Row structure
lyt_rows <- basic_table() %>%
  split_rows_by(
    var = "ACTARM",
    split_fun = drop_split_levels,
    split_label = "Treatment Group",
    label_pos = "topleft"
  ) %>%
  add_rowcounts(alt_counts = TRUE) %>%
  split_rows_by(
    var = "VISIT",
    split_fun = drop_split_levels,
    split_label = "Visit",
    label_pos = "topleft"
  ) %>%
  split_rows_by(
    var = "NOMTPT",
    split_fun = drop_split_levels,
    split_label = "Nominal Time (hr) / Timepoint",
    label_pos = "topleft",
    child_labels = "hidden"
  )

lyt <- lyt_rows %>%
  analyze_vars_in_cols(
    vars = c("AVAL", "AVALCAT1", rep("AVAL", 8)),
    .stats = c("n", "n_blq", "mean", "sd", "cv", "geom_mean", "geom_cv", "median", "min", "max"),
    .formats = c(
      n = "xx.", n_blq = "xx.", mean = format_sigfig(3), sd = format_sigfig(3), cv = "xx.x", median = format_sigfig(3),
      geom_mean = format_sigfig(3), geom_cv = "xx.x", min = format_sigfig(3), max = format_sigfig(3)
    ),
    .labels = c(
      n = "n", n_blq = "Number\nof\nLTRs/BLQs", mean = "Mean", sd = "SD", cv = "CV (%) Mean",
      geom_mean = "Geometric Mean", geom_cv = "CV % Geometric Mean", median = "Median", min = "Minimum", max = "Maximum"
    ),
    na_str = "NE",
    .aligns = "decimal"
  )

result <- build_table(lyt, df = adpc_t, alt_counts_df = adsl) %>% prune_table()

# Decorating
main_title(result) <- "Summary of PK Concentrations by Nominal Time and Treatment: PK Evaluable"
subtitles(result) <- c(
  "Protocol: xxxxx",
  paste("Analyte: ", unique(adpc_t$PARAM)),
  paste("Treatment:", unique(adpc_t$ACTARM))
)
main_footer(result) <- "NE: Not Estimable"

result
Summary of PK Concentrations by Nominal Time and Treatment: PK Evaluable
Protocol: xxxxx
Analyte:  Pharmacokinetic concentration of Xanomeline
Treatment: Xanomeline High Dose
Treatment: Xanomeline Low Dose

—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
Treatment Group                           Number                                                                                                         
  Visit                                     of                                                                                                           
    Nominal Time (hr) / Timepoint   n    LTRs/BLQs    Mean       SD      CV (%) Mean   Geometric Mean   CV % Geometric Mean   Median    Minimum   Maximum
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
Xanomeline High Dose (N=72)                                                                                                                              
  BASELINE                                                                                                                                               
    0 / Pre-dose                    72      72       0         0             NE              NE                 NE            0         0         0      
    0.08 / 5 Min Post-dose          72       0       0.100     0.00489       4.9          0.100                 4.9           0.100     0.0912    0.112  
    0.5 / 30 Min Post-dose          72       0       0.544     0.0241        4.4          0.543                 4.4           0.543     0.499     0.603  
    1 / 1h Post-dose                72       0       0.927     0.0370        4.0          0.926                 4.0           0.926     0.859     1.02   
    1.5 / 1.5h Post-dose            72       0       1.20      0.0438        3.7          1.20                  3.6           1.20      1.12      1.31   
    2 / 2h Post-dose                72       0       1.39      0.0474        3.4          1.39                  3.4           1.38      1.31      1.50   
    4 / 4h Post-dose                72       0       1.73      0.0521        3.0          1.73                  3.0           1.73      1.65      1.84   
    6 / 6h Post-dose                72       0       1.82      0.0538        3.0          1.82                  3.0           1.82      1.74      1.92   
    8 / 8h Post-dose                72       0       1.84      0.0545        3.0          1.84                  3.0           1.84      1.76      1.94   
    12 / 12h Post-dose              72       0       0.551     0.0341        6.2          0.550                 6.2           0.554     0.486     0.619  
    16 / 16h Post-dose              72       0       0.165     0.0181       11.0          0.164                11.1           0.165     0.134     0.198  
    24 / 24h Post-dose              72       0       0.0149    0.00311      20.9          0.0145               21.5           0.0146    0.0100    0.0203 
    36 / 36h Post-dose              72      72       0.00500   0             0.0          0.00500               0.0           0.00500   0.00500   0.00500
    48 / 48h Post-dose              72      72       0.00500   0             0.0          0.00500               0.0           0.00500   0.00500   0.00500
Xanomeline Low Dose (N=96)                                                                                                                               
  BASELINE                                                                                                                                               
    0 / Pre-dose                    96      96       0         0             NE              NE                 NE            0         0         0      
    0.08 / 5 Min Post-dose          96       0       0.101     0.00531       5.3          0.101                 5.3           0.100     0.0906    0.111  
    0.5 / 30 Min Post-dose          96       0       0.546     0.0263        4.8          0.545                 4.8           0.544     0.495     0.597  
    1 / 1h Post-dose                96       0       0.931     0.0406        4.4          0.930                 4.4           0.927     0.852     1.01   
    1.5 / 1.5h Post-dose            96       0       1.20      0.0481        4.0          1.20                  4.0           1.20      1.11      1.29   
    2 / 2h Post-dose                96       0       1.39      0.0518        3.7          1.39                  3.7           1.39      1.29      1.49   
    4 / 4h Post-dose                96       0       1.74      0.0547        3.2          1.74                  3.2           1.74      1.64      1.83   
    6 / 6h Post-dose                96       0       1.82      0.0548        3.0          1.82                  3.0           1.82      1.73      1.91   
    8 / 8h Post-dose                96       0       1.84      0.0548        3.0          1.84                  3.0           1.84      1.75      1.94   
    12 / 12h Post-dose              96       0       0.549     0.0297        5.4          0.548                 5.4           0.548     0.489     0.620  
    16 / 16h Post-dose              96       0       0.163     0.0163       10.0          0.163                10.0           0.161     0.135     0.200  
    24 / 24h Post-dose              96       0       0.0146    0.00288      19.7          0.0143               19.8           0.0142    0.0102    0.0207 
    36 / 36h Post-dose              96      96       0.00500   0             0.0          0.00500               0.0           0.00500   0.00500   0.00500
    48 / 48h Post-dose              96      96       0.00500   0             0.0          0.00500               0.0           0.00500   0.00500   0.00500
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

NE: Not Estimable

PK Graph

Now we create the PK graph.

# Keep only treated subjects for graph
adsl_f <- adsl %>%
  filter(SAFFL == "Y" & TRT01A != "Placebo")

# Set titles and footnotes
use_title <- "Plot of Mean (+/- SD) Plasma Concentrations Over Time by Treatment, \nPK Evaluable Patients"
use_subtitle <- "Analyte:"
use_footnote <- "Program: \nOutput:"

result <- g_lineplot(
  df = adpc,
  variables = control_lineplot_vars(
    x = "NFRLT",
    y = "AVAL",
    group_var = "ARM",
    paramcd = "PARAM",
    y_unit = "AVALU",
    subject_var = "USUBJID"
  ),
  alt_counts_df = adsl_f,
  position = ggplot2::position_dodge2(width = 0.5),
  y_lab = "Concentration",
  y_lab_add_paramcd = FALSE,
  y_lab_add_unit = TRUE,
  interval = "mean_sdi",
  whiskers = c("mean_sdi_lwr", "mean_sdi_upr"),
  title = use_title,
  subtitle = use_subtitle,
  caption = use_footnote,
  ggtheme = theme_nest()
)

plot <- result + theme(plot.caption = element_text(hjust = 0))
plot

PK Listing

Now we create an example PK listing.

# Get value of Analyte
analyte <- unique(adpc$PARAM)

# Select columns for listing
out <- adpc %>%
  select(ARM, USUBJID, VISIT, NFRLT, AFRLT, AVALCAT1)

# Add descriptive labels
var_labels(out) <- c(
  ARM = "Treatment Group",
  USUBJID = "Subject ID",
  VISIT = "Visit",
  NFRLT = paste0("Nominal\nSampling\nTime (", adpc$RRLTU[1], ")"),
  AFRLT = paste0("Actual Time\nFrom First\nDose (", adpc$RRLTU[1], ")"),
  AVALCAT1 = paste0("Concentration\n(", adpc$AVALU[1], ")")
)

# Create listing
lsting <- as_listing(
  out,
  key_cols = c("ARM", "USUBJID", "VISIT"),
  disp_cols = names(out),
  default_formatting = list(
    all = fmt_config(align = "left"),
    numeric = fmt_config(
      format = "xx.xx",
      na_str = " ",
      align = "right"
    )
  ),
  main_title = paste(
    "Listing of",
    analyte,
    "Concentration by Treatment Group, Subject and Nominal Time, PK Population\nProtocol: xxnnnnn"
  ),
  subtitles = paste("Analyte:", analyte)
)

head(lsting, 28)
Listing of Pharmacokinetic concentration of Xanomeline Concentration by Treatment Group, Subject and Nominal Time, PK Population
Protocol: xxnnnnn
Analyte: Pharmacokinetic concentration of Xanomeline

——————————————————————————————————————————————————————————————————————————————————————
                                                Nominal    Actual Time                
                                                Sampling   From First    Concentration
  Treatment Group      Subject ID     Visit     Time (h)    Dose (h)        (ug/ml)   
——————————————————————————————————————————————————————————————————————————————————————
Xanomeline High Dose   01-701-1028   BASELINE       0.00         -0.50   <BLQ         
                                                    0.08          0.08   0.102        
                                                    0.50          0.50   0.547        
                                                    1.00          1.00   0.925        
                                                    1.50          1.50   1.19         
                                                    2.00          2.00   1.37         
                                                    4.00          4.00   1.68         
                                                    6.00          6.00   1.76         
                                                    8.00          8.00   1.77         
                                                   12.00         12.00   0.495        
                                                   16.00         16.00   0.138        
                                                   24.00         24.00   0.0107       
                                                   36.00         36.00   <BLQ         
                                                   48.00         48.00   <BLQ         
                       01-701-1034   BASELINE       0.00         -0.50   <BLQ         
                                                    0.08          0.08   0.105        
                                                    0.50          0.50   0.569        
                                                    1.00          1.00   0.967        
                                                    1.50          1.50   1.25         
                                                    2.00          2.00   1.44         
                                                    4.00          4.00   1.79         
                                                    6.00          6.00   1.88         
                                                    8.00          8.00   1.9          
                                                   12.00         12.00   0.556        
                                                   16.00         16.00   0.162        
                                                   24.00         24.00   0.0138       
                                                   36.00         36.00   <BLQ         
                                                   48.00         48.00   <BLQ         
Adverse Events
teal applications
Source Code
---
title: "Pharmacokinetic"
order: 3
---


```{r setup script, include=FALSE, purl=FALSE}
invisible_hook_purl <- function(before, options, ...) {
  knitr::hook_purl(before, options, ...)
  NULL
}
knitr::knit_hooks$set(purl = invisible_hook_purl)
```

## Introduction

This guide will show you how pharmaverse packages, along with some from tidyverse, can be used to create pharmacokinetic (PK) tables, listings and graphs, using the `{pharmaverseadam}` `ADSL` and `ADPC` data as an input.

The packages used with a brief description of their purpose are as follows:

* [`{rtables}`](https://insightsengineering.github.io/rtables/): designed to create and display complex tables with R.
* [`{tern}`](https://insightsengineering.github.io/tern/): contains analysis functions to create tables and graphs used for clinical trial reporting. 
* [`{rlistings}`](https://insightsengineering.github.io/rlistings/): contains framework for creating listings for clinical reporting. 

See catalog for PK TLGs here [PK TLG catalog](https://insightsengineering.github.io/tlg-catalog/stable/)

See the [`{admiral}` Guide for creating a PK NCA ADaM](https://pharmaverse.github.io/admiral/articles/pk_adnca.html) for more information about the structure of `ADPC`. See also `ADPC` under the ADaM section on the left panel.

## Data preprocessing

Here we set up the data for the table, graph and listing.  We will read `ADPC` and `ADSL` from `{pharmaverseadam}`. We use `tern::df_explicit_na()` to set missing values as categorical. In `ADPC` we will keep only concentration records (dropping dosing records), and for this example we will only keep plasma concentrations (dropping urine). The `ADPC` data also includes duplicated records for analysis with `DYTPE == "COPY"` we will drop these as well (These are removed by selecting `ANL02FL == "Y"`).

```{r setup, message=FALSE, warning=FALSE, results='hold'}
library(pharmaverseadam)
library(tern)
library(dplyr)
library(ggplot2)
library(nestcolor)
library(rlistings)

# Read data from pharmaverseadam
adpc <- pharmaverseadam::adpc
adsl <- pharmaverseadam::adsl

# Use tern::df_explicit_na() to end encode missing values as categorical
adsl <- adsl %>%
  df_explicit_na()

adpc <- adpc %>%
  df_explicit_na()

# For ADPC keep only concentration records and treated subjects
# Keep only plasma records for this example
# Remove DTYPE = COPY records with ANL02FL == "Y"
adpc <- adpc %>%
  filter(PARAMCD != "DOSE" & TRT01A != "Placebo" & PARCAT1 == "PLASMA" & ANL02FL == "Y")

```

## PK Table

Now we create the PK table.

```{r table}
# Setting up the data for table
adpc_t <- adpc %>%
  mutate(
    NFRLT = as.factor(NFRLT),
    AVALCAT1 = as.factor(AVALCAT1),
    NOMTPT = as.factor(paste(NFRLT, "/", PCTPT))
  ) %>%
  select(NOMTPT, ACTARM, VISIT, AVAL, PARAM, AVALCAT1)

adpc_t$NOMTPT <- factor(
  adpc_t$NOMTPT,
  levels = levels(adpc_t$NOMTPT)[order(as.numeric(gsub(".*?([0-9\\.]+).*", "\\1", levels(adpc_t$NOMTPT))))]
)

# Row structure
lyt_rows <- basic_table() %>%
  split_rows_by(
    var = "ACTARM",
    split_fun = drop_split_levels,
    split_label = "Treatment Group",
    label_pos = "topleft"
  ) %>%
  add_rowcounts(alt_counts = TRUE) %>%
  split_rows_by(
    var = "VISIT",
    split_fun = drop_split_levels,
    split_label = "Visit",
    label_pos = "topleft"
  ) %>%
  split_rows_by(
    var = "NOMTPT",
    split_fun = drop_split_levels,
    split_label = "Nominal Time (hr) / Timepoint",
    label_pos = "topleft",
    child_labels = "hidden"
  )

lyt <- lyt_rows %>%
  analyze_vars_in_cols(
    vars = c("AVAL", "AVALCAT1", rep("AVAL", 8)),
    .stats = c("n", "n_blq", "mean", "sd", "cv", "geom_mean", "geom_cv", "median", "min", "max"),
    .formats = c(
      n = "xx.", n_blq = "xx.", mean = format_sigfig(3), sd = format_sigfig(3), cv = "xx.x", median = format_sigfig(3),
      geom_mean = format_sigfig(3), geom_cv = "xx.x", min = format_sigfig(3), max = format_sigfig(3)
    ),
    .labels = c(
      n = "n", n_blq = "Number\nof\nLTRs/BLQs", mean = "Mean", sd = "SD", cv = "CV (%) Mean",
      geom_mean = "Geometric Mean", geom_cv = "CV % Geometric Mean", median = "Median", min = "Minimum", max = "Maximum"
    ),
    na_str = "NE",
    .aligns = "decimal"
  )

result <- build_table(lyt, df = adpc_t, alt_counts_df = adsl) %>% prune_table()

# Decorating
main_title(result) <- "Summary of PK Concentrations by Nominal Time and Treatment: PK Evaluable"
subtitles(result) <- c(
  "Protocol: xxxxx",
  paste("Analyte: ", unique(adpc_t$PARAM)),
  paste("Treatment:", unique(adpc_t$ACTARM))
)
main_footer(result) <- "NE: Not Estimable"

result
```


## PK Graph

Now we create the PK graph.

```{r graph}

# Keep only treated subjects for graph
adsl_f <- adsl %>%
  filter(SAFFL == "Y" & TRT01A != "Placebo")

# Set titles and footnotes
use_title <- "Plot of Mean (+/- SD) Plasma Concentrations Over Time by Treatment, \nPK Evaluable Patients"
use_subtitle <- "Analyte:"
use_footnote <- "Program: \nOutput:"

result <- g_lineplot(
  df = adpc,
  variables = control_lineplot_vars(
    x = "NFRLT",
    y = "AVAL",
    group_var = "ARM",
    paramcd = "PARAM",
    y_unit = "AVALU",
    subject_var = "USUBJID"
  ),
  alt_counts_df = adsl_f,
  position = ggplot2::position_dodge2(width = 0.5),
  y_lab = "Concentration",
  y_lab_add_paramcd = FALSE,
  y_lab_add_unit = TRUE,
  interval = "mean_sdi",
  whiskers = c("mean_sdi_lwr", "mean_sdi_upr"),
  title = use_title,
  subtitle = use_subtitle,
  caption = use_footnote,
  ggtheme = theme_nest()
)

plot <- result + theme(plot.caption = element_text(hjust = 0))
plot
```

## PK Listing

Now we create an example PK listing.

```{r listing}
# Get value of Analyte
analyte <- unique(adpc$PARAM)

# Select columns for listing
out <- adpc %>%
  select(ARM, USUBJID, VISIT, NFRLT, AFRLT, AVALCAT1)

# Add descriptive labels
var_labels(out) <- c(
  ARM = "Treatment Group",
  USUBJID = "Subject ID",
  VISIT = "Visit",
  NFRLT = paste0("Nominal\nSampling\nTime (", adpc$RRLTU[1], ")"),
  AFRLT = paste0("Actual Time\nFrom First\nDose (", adpc$RRLTU[1], ")"),
  AVALCAT1 = paste0("Concentration\n(", adpc$AVALU[1], ")")
)

# Create listing
lsting <- as_listing(
  out,
  key_cols = c("ARM", "USUBJID", "VISIT"),
  disp_cols = names(out),
  default_formatting = list(
    all = fmt_config(align = "left"),
    numeric = fmt_config(
      format = "xx.xx",
      na_str = " ",
      align = "right"
    )
  ),
  main_title = paste(
    "Listing of",
    analyte,
    "Concentration by Treatment Group, Subject and Nominal Time, PK Population\nProtocol: xxnnnnn"
  ),
  subtitles = paste("Analyte:", analyte)
)

head(lsting, 28)
```


 
Cookie Preferences