Introduction to Superposition in Pumas

Author

Vijay Ivaturi

Load the necessary libraries

using DataFramesMeta
using NCA
using PharmaDatasets
using Random
using CairoMakie
using AlgebraOfGraphics

1 Introduction

This tutorial will introduce the workflow of performing Superposition (SP) in a command-line based workflow which simulates concentration-time profiles following multiple dosing events based on single-dose data. A graphical user interface for SP is also available for a more interactive experience. Please contact sales@pumas.ai for more information.

For the purpose of this tutorial, we will be using the following dataset:

Blood samples for pharmacokinetics were collected in 30 subjects who received 250 mg, 500 mg and 750 mg of DrugX as a single dose for 24 hours post dose.

We will perform superposition using the single dose data for each dosing group.

In the given dataset

  • Time = hrs
  • Conc = ug/L
  • Amt = ug

1.1 Load Data

Load the pharmacokinetic (PK) dataset using the datasets function from PharmaDatasets.

sd_pk = dataset("iv_sd_1")
sd_pk_250 = @rsubset sd_pk :dose == 250;
first(sd_pk, 5)
5×6 DataFrame
Row id dose time conc amt route
Int64 Int64 Float64 Float64 Int64? String3
1 1 250 0.0 17.29 250 iv
2 1 250 0.25 17.87 missing iv
3 1 250 0.5 25.59 missing iv
4 1 250 0.75 19.52 missing iv
5 1 250 1.0 18.72 missing iv

2 Superposition in Pumas

The NCA.superposition function in Pumas can be used to simulate drug concentration profiles after multiple dosing events, based on single-dose data. Superposition analysis is especially useful for predicting steady-state concentrations without having to collect data from multiple dosing trials. Here, we will explore the details of each parameter involved in the NCA.superposition function, walk through its use, and provide examples to help you understand how to apply it in practice.

2.1 Function Overview: NCA.superposition

NCA.superposition(
    df::DataFrame;
    assumed_tau,
    n_doses = Inf,
    loading_dose = nothing,
    maintenance_dose = nothing,
    method = :linear,
)

This function calculates the superposition of concentration profiles from single-dose data, simulating what would happen after repeated dosing. The superposition technique assumes that the pharmacokinetics of the drug remain linear and predictable across multiple doses.

2.1.1 Arguments:

  1. assumed_tau - Interdose Interval (Required for a simple schedule)

    • The interdose interval is the time between consecutive doses. This is a crucial parameter because it dictates how often the drug is administered.
    • In practice, this value is typically provided in hours or days. For example, if a drug is dosed once daily, you would set assumed_tau = 24 (assuming time is measured in hours).

    Example:

    assumed_tau = 24  # Once-a-day dosing
  2. n_doses - Number of Doses (Optional)

    • n_doses specifies the total number of doses administered. This can either be a positive integer or infinity (Inf, the default). With Inf the number of doses is chosen automatically from the estimated terminal half-life, and max_auto_doses puts an upper bound on that automatic choice.
    • If the number of doses is set to a finite number (e.g., n_doses = 5), the function will calculate the concentration-time profile up to the specified number of doses.

    Example:

    n_doses = 7  # Administer 7 doses
  3. loading_dose and maintenance_dose - Dose Amounts (Optional)

    • These parameters define the amount of drug administered with the first dose and with all subsequent doses. If they are not provided, the dose of the observed profile is used.
    • Specifying them allows you to simulate superposition for different dosing levels without altering the original dataset.

    Example:

    loading_dose = 20000      # Set the first dose to 20,000 units
    maintenance_dose = 20000  # Set every following dose to 20,000 units
  4. dose_times and doses - Complex Schedules (Optional)

    • Instead of assumed_tau, a schedule that is not evenly spaced is given as a vector of dose times (starting at 0) and a vector of dose amounts of the same length.

    Example:

    dose_times = [0, 12, 36]  # Doses at 0, 12 and 36 hours
    doses = [20000, 10000, 10000]
  5. method - Interpolation Method (Optional)

    • The method parameter defines how the concentration values are interpolated between observed data points. The default method is :linear, meaning the function will use linear interpolation to predict concentrations at unmeasured time points.

    Example:

    method = :linear  # Use linear interpolation

3 How Superposition Works

The superposition principle is based on the concept of linear pharmacokinetics, where the concentration profile following multiple dosing events can be predicted by summing up the individual contributions of each dose. This means the concentration at any given time point after multiple doses is simply the sum of the concentrations produced by each individual dose, shifted by the interdose interval.

4 Example Workflow: Superposition for a 7-Day Dosing Regimen

Let’s walk through a practical example where we simulate the concentration-time profile after a 7-day dosing regimen with a once-daily (24-hour interval) dosing schedule.

4.1 Step 1: Prepare Your NCA Data

First, you need to read the observed data. The dataset carries the dose on a single row per subject and missing on the observation rows, so it is read with read_nca_pumas, specifying the important variables (e.g. time, dose, concentration):

nca_pop = read_nca_pumas(
    sd_pk_250;  # DataFrame containing PK data
    id = :id,  # Subject ID
    time = :time,  # Time column
    concentration = :conc,  # Drug concentration column
    dose = :amt,  # Dose amount
    route = :route,  # Dosing route (e.g. ivbolus or ev)
)
160×12 DataFrame
135 rows omitted
Row id profile time nominal_time dose_time nominal_dose_time concentration dose route duration exclusion exclusion_mask
Int64 Int64 Float64 Float64 Float64 Float64 Float64 Float64 String Missing Missing Bool
1 1 1 0.0 0.0 0.0 0.0 17.29 250.0 iv missing missing false
2 1 1 0.25 0.25 0.0 0.0 17.87 250.0 iv missing missing false
3 1 1 0.5 0.5 0.0 0.0 25.59 250.0 iv missing missing false
4 1 1 0.75 0.75 0.0 0.0 19.52 250.0 iv missing missing false
5 1 1 1.0 1.0 0.0 0.0 18.72 250.0 iv missing missing false
6 1 1 1.5 1.5 0.0 0.0 19.74 250.0 iv missing missing false
7 1 1 2.0 2.0 0.0 0.0 16.8 250.0 iv missing missing false
8 1 1 2.5 2.5 0.0 0.0 18.76 250.0 iv missing missing false
9 1 1 3.0 3.0 0.0 0.0 17.12 250.0 iv missing missing false
10 1 1 4.0 4.0 0.0 0.0 12.45 250.0 iv missing missing false
11 1 1 6.0 6.0 0.0 0.0 10.51 250.0 iv missing missing false
12 1 1 8.0 8.0 0.0 0.0 8.55 250.0 iv missing missing false
13 1 1 12.0 12.0 0.0 0.0 6.57 250.0 iv missing missing false
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
149 10 10 1.0 1.0 0.0 0.0 28.68 250.0 iv missing missing false
150 10 10 1.5 1.5 0.0 0.0 27.59 250.0 iv missing missing false
151 10 10 2.0 2.0 0.0 0.0 29.09 250.0 iv missing missing false
152 10 10 2.5 2.5 0.0 0.0 22.58 250.0 iv missing missing false
153 10 10 3.0 3.0 0.0 0.0 17.47 250.0 iv missing missing false
154 10 10 4.0 4.0 0.0 0.0 21.47 250.0 iv missing missing false
155 10 10 6.0 6.0 0.0 0.0 8.82 250.0 iv missing missing false
156 10 10 8.0 8.0 0.0 0.0 7.09 250.0 iv missing missing false
157 10 10 12.0 12.0 0.0 0.0 2.68 250.0 iv missing missing false
158 10 10 16.0 16.0 0.0 0.0 1.01 250.0 iv missing missing false
159 10 10 20.0 20.0 0.0 0.0 0.46 250.0 iv missing missing false
160 10 10 24.0 24.0 0.0 0.0 0.15 250.0 iv missing missing false

4.2 Step 2: Run Superposition Analysis

Next, run the superposition analysis for a 7-day, once-daily dosing regimen:

result = NCA.superposition(
    nca_pop;
    assumed_tau = 24,
    n_doses = 7,
    loading_dose = 20000,
    maintenance_dose = 20000,
)
1120×15 DataFrame
1095 rows omitted
Row id profile time nominal_time dose_time nominal_dose_time concentration dose route duration exclusion exclusion_mask superposition_dose_number superposition_error superposition_assumed_tau
Int64 Int64 Float64 Float64 Float64 Float64 Float64? Int64 String Missing Missing Bool Int64 String? Int64
1 1 1 0.0 0.0 0.0 0.0 1383.2 20000 iv missing missing false 1 missing 24
2 1 1 0.25 0.25 0.0 0.0 1429.6 20000 iv missing missing false 1 missing 24
3 1 1 0.5 0.5 0.0 0.0 2047.2 20000 iv missing missing false 1 missing 24
4 1 1 0.75 0.75 0.0 0.0 1561.6 20000 iv missing missing false 1 missing 24
5 1 1 1.0 1.0 0.0 0.0 1497.6 20000 iv missing missing false 1 missing 24
6 1 1 1.5 1.5 0.0 0.0 1579.2 20000 iv missing missing false 1 missing 24
7 1 1 2.0 2.0 0.0 0.0 1344.0 20000 iv missing missing false 1 missing 24
8 1 1 2.5 2.5 0.0 0.0 1500.8 20000 iv missing missing false 1 missing 24
9 1 1 3.0 3.0 0.0 0.0 1369.6 20000 iv missing missing false 1 missing 24
10 1 1 4.0 4.0 0.0 0.0 996.0 20000 iv missing missing false 1 missing 24
11 1 1 6.0 6.0 0.0 0.0 840.8 20000 iv missing missing false 1 missing 24
12 1 1 8.0 8.0 0.0 0.0 684.0 20000 iv missing missing false 1 missing 24
13 1 1 12.0 12.0 0.0 0.0 525.6 20000 iv missing missing false 1 missing 24
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
1109 10 10 1.0 1.0 144.0 144.0 2304.47 20000 iv missing missing false 7 missing 24
1110 10 10 1.5 1.5 144.0 144.0 2216.15 20000 iv missing missing false 7 missing 24
1111 10 10 2.0 2.0 144.0 144.0 2335.15 20000 iv missing missing false 7 missing 24
1112 10 10 2.5 2.5 144.0 144.0 1813.46 20000 iv missing missing false 7 missing 24
1113 10 10 3.0 3.0 144.0 144.0 1403.87 20000 iv missing missing false 7 missing 24
1114 10 10 4.0 4.0 144.0 144.0 1722.55 20000 iv missing missing false 7 missing 24
1115 10 10 6.0 6.0 144.0 144.0 708.682 20000 iv missing missing false 7 missing 24
1116 10 10 8.0 8.0 144.0 144.0 569.119 20000 iv missing missing false 7 missing 24
1117 10 10 12.0 12.0 144.0 144.0 215.144 20000 iv missing missing false 7 missing 24
1118 10 10 16.0 16.0 144.0 144.0 81.0886 20000 iv missing missing false 7 missing 24
1119 10 10 20.0 20.0 144.0 144.0 36.9119 20000 iv missing missing false 7 missing 24
1120 10 10 24.0 24.0 144.0 144.0 12.0434 20000 iv missing missing false 7 missing 24
  • assumed_tau=24: The drug is administered once every 24 hours.
  • n_doses=7: We are simulating a 7-day dosing schedule.
  • loading_dose=20000 and maintenance_dose=20000: Each dose consists of 20,000 units of the drug.

4.3 Step 3: Visualize the Superposition Results

After running the superposition, you can visualize the predicted concentration-time profiles. You might want to plot the results to compare steady-state behavior. The time column of the output is the time after the dose of its interval, so the absolute time of an observation is dose_time + time:

# Example: Use AlgebraOfGraphics for plotting
result_abstime = @rtransform result :abstime = :dose_time + :time
plt =
    data(result_abstime) *
    mapping(:abstime, :concentration, group = :id => nonnumeric) *
    visual(Lines, color = (:grey, 0.5))
draw(
    plt;
    axis = (;
        xlabel = "Time (hours)",
        ylabel = "Concentration (ug/L)",
        xticks = 0:12:160,
        yticks = 0:500:4000,
    ),
)

In this plot, you’ll observe how the drug concentrations change over time with each subsequent dose. By day 7, the drug may reach steady-state, meaning the concentration profile no longer changes significantly from one dose to the next.

4.4 Step 4: Let the Number of Doses Be Determined Automatically

Leaving n_doses at its default of Inf simulates as many doses as the estimated terminal half-life suggests are needed, which is a convenient way of covering the approach to steady state without picking a number by hand.

result_auto = NCA.superposition(nca_pop; assumed_tau = 24)
maximum(result_auto.superposition_dose_number)
3

5 Performing NCA on Superposition Result

The result of the superposition analysis can easily be sent to compute NCA metrics. Each dose interval is its own profile, and the interval of interest, here the last one, is marked as steady state by setting its tau:

sup_input = select(
    result,
    Not([
        :profile,
        :dose_time,
        :nominal_dose_time,
        :exclusion_mask,
        :superposition_error,
        :superposition_assumed_tau,
        :superposition_dose_number,
    ]),
    :superposition_dose_number => :dose_number,
)
sup_input.tau =
    ifelse.(sup_input.dose_number .== maximum(sup_input.dose_number), 24.0, missing)
nca_sup = read_nca(sup_input; group = [:dose_number])
1120×14 DataFrame
1095 rows omitted
Row dose_number id profile time nominal_time dose_time nominal_dose_time concentration dose route duration tau exclusion exclusion_mask
Int64 Int64 Int64 Float64 Float64 Float64 Float64 Float64 Float64 String Missing Float64? Missing Bool
1 1 1 1 0.0 0.0 0.0 0.0 1383.2 20000.0 iv missing missing missing false
2 1 1 1 0.25 0.25 0.0 0.0 1429.6 20000.0 iv missing missing missing false
3 1 1 1 0.5 0.5 0.0 0.0 2047.2 20000.0 iv missing missing missing false
4 1 1 1 0.75 0.75 0.0 0.0 1561.6 20000.0 iv missing missing missing false
5 1 1 1 1.0 1.0 0.0 0.0 1497.6 20000.0 iv missing missing missing false
6 1 1 1 1.5 1.5 0.0 0.0 1579.2 20000.0 iv missing missing missing false
7 1 1 1 2.0 2.0 0.0 0.0 1344.0 20000.0 iv missing missing missing false
8 1 1 1 2.5 2.5 0.0 0.0 1500.8 20000.0 iv missing missing missing false
9 1 1 1 3.0 3.0 0.0 0.0 1369.6 20000.0 iv missing missing missing false
10 1 1 1 4.0 4.0 0.0 0.0 996.0 20000.0 iv missing missing missing false
11 1 1 1 6.0 6.0 0.0 0.0 840.8 20000.0 iv missing missing missing false
12 1 1 1 8.0 8.0 0.0 0.0 684.0 20000.0 iv missing missing missing false
13 1 1 1 12.0 12.0 0.0 0.0 525.6 20000.0 iv missing missing missing false
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
1109 7 10 70 1.0 1.0 0.0 0.0 2304.47 20000.0 iv missing 24.0 missing false
1110 7 10 70 1.5 1.5 0.0 0.0 2216.15 20000.0 iv missing 24.0 missing false
1111 7 10 70 2.0 2.0 0.0 0.0 2335.15 20000.0 iv missing 24.0 missing false
1112 7 10 70 2.5 2.5 0.0 0.0 1813.46 20000.0 iv missing 24.0 missing false
1113 7 10 70 3.0 3.0 0.0 0.0 1403.87 20000.0 iv missing 24.0 missing false
1114 7 10 70 4.0 4.0 0.0 0.0 1722.55 20000.0 iv missing 24.0 missing false
1115 7 10 70 6.0 6.0 0.0 0.0 708.682 20000.0 iv missing 24.0 missing false
1116 7 10 70 8.0 8.0 0.0 0.0 569.119 20000.0 iv missing 24.0 missing false
1117 7 10 70 12.0 12.0 0.0 0.0 215.144 20000.0 iv missing 24.0 missing false
1118 7 10 70 16.0 16.0 0.0 0.0 81.0886 20000.0 iv missing 24.0 missing false
1119 7 10 70 20.0 20.0 0.0 0.0 36.9119 20000.0 iv missing 24.0 missing false
1120 7 10 70 24.0 24.0 0.0 0.0 12.0434 20000.0 iv missing 24.0 missing false

Once the result of the superposition data is read into Pumas as a NCA dataset, we can run NCA on this.

nca_sup_result = run_nca(nca_sup, [:cmax, :cmin, :cavg, :auctau_obs, :fluctuation])
@rsubset nca_sup_result :dose_number == maximum(nca_sup_result.dose_number)
10×16 DataFrame
Row dose_number id profile dose route duration tau assumed_tau cmax cmin cavg cavg_err auctau_obs auctau_obs_err fluctuation fluctuation_err
Int64 Int64 Int64 Float64 String Missing Float64? Missing Float64 Float64 Float64? String? Float64? String? Float64? String?
1 7 1 7 20000.0 iv missing 24.0 missing 2182.39 158.162 662.679 missing 15904.3 missing 305.461 missing
2 7 2 14 20000.0 iv missing 24.0 missing 3440.81 4.80841 543.216 missing 13037.2 missing 632.53 missing
3 7 3 21 20000.0 iv missing 24.0 missing 3554.18 45.4231 608.441 missing 14602.6 missing 576.679 missing
4 7 4 28 20000.0 iv missing 24.0 missing 2797.58 72.7555 666.473 missing 15995.3 missing 408.843 missing
5 7 5 35 20000.0 iv missing 24.0 missing 2593.5 52.2764 657.548 missing 15781.2 missing 386.469 missing
6 7 6 42 20000.0 iv missing 24.0 missing 1997.57 76.7701 507.953 missing 12190.9 missing 378.145 missing
7 7 7 49 20000.0 iv missing 24.0 missing 3159.66 6.4166 490.585 missing 11774.0 missing 642.753 missing
8 7 8 56 20000.0 iv missing 24.0 missing 2743.35 57.1223 548.178 missing 13156.3 missing 490.029 missing
9 7 9 63 20000.0 iv missing 24.0 missing 2453.85 30.6978 626.216 missing 15029.2 missing 386.952 missing
10 7 10 70 20000.0 iv missing 24.0 missing 3088.14 12.0434 599.246 missing 14381.9 missing 513.328 missing

The endpoints that predict steady state from a single dose, cmaxss, cminss, cavgss and accumulation_index, are computed on the single-dose data itself by giving it the hypothetical dosing interval as assumed_tau:

sd_pk_250_tau = @rtransform sd_pk_250 :assumed_tau = 24.0
nca_pop_tau = read_nca_pumas(
    sd_pk_250_tau;
    id = :id,
    time = :time,
    concentration = :conc,
    dose = :amt,
    route = :route,
    assumed_tau = :assumed_tau,
)
run_nca(nca_pop_tau, [:cmaxss, :cminss, :cavgss, :accumulation_index])
10×11 DataFrame
Row id profile dose route duration tau assumed_tau cmaxss cminss cavgss accumulation_index
Int64 Int64 Float64 String Missing Missing Float64 Float64 Float64 Float64 Float64
1 1 1 250.0 iv missing missing 24.0 27.2699 1.97622 8.27811 1.08327
2 2 2 250.0 iv missing missing 24.0 43.01 0.0601049 6.79005 1.00157
3 3 3 250.0 iv missing missing 24.0 44.4198 0.567676 7.60378 1.01462
4 4 4 250.0 iv missing missing 24.0 34.9518 0.90897 8.32803 1.02493
5 5 5 250.0 iv missing missing 24.0 32.4058 0.653169 8.21741 1.02169
6 6 6 250.0 iv missing missing 24.0 24.9679 0.959553 6.34669 1.04497
7 7 7 250.0 iv missing missing 24.0 39.4956 0.080207 6.13209 1.00258
8 8 8 250.0 iv missing missing 24.0 34.2911 0.714001 6.85023 1.03468
9 9 9 250.0 iv missing missing 24.0 30.6698 0.383687 7.82658 1.00966
10 10 10 250.0 iv missing missing 24.0 38.6013 0.150541 7.49017 1.00341

6 Practical Applications of Superposition

Superposition analysis is widely used in pharmacokinetics for the following purposes:

  1. Dosing Regimen Design:

    • Helps to predict the concentration profile of a drug after multiple doses and determine whether therapeutic concentrations are achieved and maintained.
  2. Steady-State Prediction:

    • Essential for drugs that require steady-state concentrations for efficacy, such as chronic therapies or antibiotics.
  3. Dose Adjustment:

    • By adjusting the dose amounts, superposition can help simulate different dose levels and guide decisions on appropriate dosing adjustments.
  4. Interdose Interval Analysis:

    • By varying assumed_tau, you can simulate different dosing intervals (e.g., twice daily vs. once daily) to optimize the regimen for efficacy and safety.

7 Conclusion

The NCA.superposition function in Pumas is a flexible and essential tool for pharmacokinetic modeling. By simulating drug concentration profiles under repeated dosing, you can make informed decisions about dosing regimens, steady-state predictions, and dose adjustments. With this detailed understanding of the function parameters and a step-by-step approach, you can apply superposition analysis effectively to your own datasets.