Data Parsing

NCA.jl reads pharmacokinetic data from DataFrames. Use read_nca for tidy (long) format data where each row is one observation. Use read_nca_pumas for Pumas simulation tables and other NONMEM-style event tables, where dose rows are interleaved with the observation rows.

read_nca

NCA.read_nca — Function
read_nca(df; kwargs...)

Reads a DataFrame with NCA input data in long format where each row is one observation, and returns a DataFrame that can be passed to run_nca. Dosing events are specified with values repeated across all observation rows of a given profile.

Note

Prior to NCA.jl v3.0, read_nca heuristically decided which data format it was dealing with. From v3.0 on, read_nca handles only the "tidy" or long format where each row is one observation and dosing information including dose time is repeated across all rows of a given profile.

Required columns (Plasma & Urine)

  • id: The identifiers for each subject. If there are multiple profiles per subject, sufficient grouping columns have to be given via the group keyword. Can be numeric or string.
  • dose: The amount of drug administered.
  • concentration: The observed concentrations or amounts in plasma or urine.
  • route: The route of administration. Accepted values (case-insensitive): "ivbolus" (IV bolus), "ivinfusion" (IV infusion), "ev", "po", "im", "sc", or "oral" (extravascular). The legacy names "iv" and "inf" are also accepted. May be missing to leave the route unspecified; run_nca then reports route-dependent endpoints as missing, with the reason in the [endpoint]_err column.
  • duration (only required for infusion routes): The duration of the infusion. Must be positive.

Required columns (Plasma)

  • time: The times at which observations were made. Will be transformed to time-after-dose given the dose time information passed via dose_time.

Required columns (Urine)

  • start_time: The start times for urine collection intervals. Will be transformed to time-after-dose given the dose time information passed via dose_time.
  • end_time: The end times for urine collection intervals. Will be transformed to time-after-dose given the dose time information passed via dose_time.
  • volume: The urine volume for each collection interval.

Optional columns (Plasma & Urine)

  • group: Additional grouping columns beyond id to separate profiles when there are multiple profiles per subject (e.g., different treatments, periods, or formulations). Can be given as one :group or multiple [:groupA, :groupB] symbols.
  • dose_time: The time at which the dose was administered. This will be used to transform time for plasma or start_time and end_time for urine analyses into time-after-dose (TAD). Like all dose-related columns, this value must be repeated across every observation row of the profile. If this column is not specified, it will be set to all zeros which means that time is assumed to be given as time-after-dose.
  • nominal_dose_time: The nominal (planned) time at which the dose was administered. If not provided, defaults to the actual dose_time values. This will be used to transform nominal_time for plasma or nominal_start_time and nominal_end_time for urine analyses into time-after-dose (TAD) format. If those columns are already in TAD format, nominal_dose_time should be zero to avoid further transformation.
  • tau: The dosing interval for steady-state dosing. Must be positive where it is not missing.
  • blq: Below limit of quantification flag. Boolean values indicating whether observations are below the limit of quantification. See transform_nca (the blq_rules keyword) for how flagged values are substituted.
  • lambdaz_mask: Boolean mask indicating which observations should be included in the terminal elimination phase (λz) calculation. Rows with a value of true are included. If the column is absent, or all-missing for a profile, that profile's terminal points are selected automatically instead. See lambdaz_fit for a worked example.
  • exclusion: Reason string(s) for excluding individual observations from the analysis. Use missing (or omit the column) for observations to keep, and separate multiple reasons for one observation with newlines. read_nca derives a boolean exclusion_mask column from it (true where a reason is present). Excluded observations are never deleted and their values are left untouched: both columns remain in the read_nca/transform_nca data frame so the flagged points stay visible (e.g. for plotting), and the observations are skipped by every endpoint when you call run_nca.

Optional columns (Plasma)

  • nominal_time: The nominal (planned) times at which observations were made. If not provided, defaults to the time column. Nominal times are not used for NCA calculations but may be used for grouping when creating summary TLFs.

Optional columns (Urine)

  • nominal_start_time: The nominal (planned) start times for urine collection intervals. If not provided, defaults to the start_time column. Nominal times are not used for NCA calculations but may be used for grouping when creating summary TLFs.
  • nominal_end_time: The nominal (planned) end times for urine collection intervals. If not provided, defaults to the end_time column. Nominal times are not used for NCA calculations but may be used for grouping when creating summary TLFs.

Units

Several keywords allow you to add units for different sets of columns. Units from Unitful.jl and DynamicQuantities.jl are supported. Alternatively, you can pass a DataFrame with columns that already contain data with units.

  • concentration_unit: Unit for concentration / observation values.
  • time_unit: Unit for time values.
  • dose_unit: Unit for amount/dose values.
  • volume_unit: Unit for volume values.

Examples

Plasma analysis. The trailing ; on read_nca suppresses the wide DataFrame output; the exclusion column carries reason strings from which read_nca derives the boolean exclusion_mask shown below:

julia> using NCA, DataFrames

julia> df = DataFrame(
           ID = [1, 1, 1, 2, 2, 2],
           TAD = [0, 1, 2, 0, 1, 2],
           CONC = [0.1, 0.5, 0.3, 0.2, 1.0, 0.4],
           AMT = [50, 50, 50, 100, 100, 100],
           DOSETIME = [0, 0, 0, 0, 0, 0],
           ROUTE = ["IVBOLUS", "IVBOLUS", "IVBOLUS", "EV", "EV", "EV"],
           exclusion = [missing, missing, "assay error", missing, missing, missing],
       );

julia> df_plasma = read_nca(df, time = :TAD, concentration = :CONC, dose = :AMT, dose_time = :DOSETIME);

julia> df_plasma.exclusion_mask   # derived from the `exclusion` reasons; this observation is skipped by run_nca
6-element Vector{Bool}:
 0
 0
 1
 0
 0
 0

Urine analysis:

julia> using NCA, DataFrames

julia> df = DataFrame(
           SUBJ = ["A", "A", "A", "B", "B", "B"],
           START = [0, 1, 2, 24, 25, 26],
           STOP = [1, 2, 3, 25, 26, 27],
           DOSE_TIME = [0, 0, 0, 24, 24, 24],
           VOL = [4, 5, 3, 2, 5, 6],
           CONC = [3, 4, 3, 5, 4, 3],
           AMOUNT = [30, 30, 30, 50, 50, 50],
           ROUTE = ["IVBOLUS", "IVBOLUS", "IVBOLUS", "IVBOLUS", "IVBOLUS", "IVBOLUS"],
           SEX = ["M", "M", "M", "F", "F", "F"],
       );

julia> df_urine = read_nca(
           df,
           id = :SUBJ,
           start_time = :START,
           end_time = :STOP,
           concentration = :CONC,
           volume = :VOL,
           dose = :AMOUNT,
           dose_time = :DOSE_TIME,
           group = :SEX,
       );

read_nca_pumas

NCA.read_nca_pumas — Function
read_nca_pumas(df; kwargs...)

Read a DataFrame with NCA input data and return a DataFrame that can be passed to run_nca. The data must be formatted such that dosing rows are interleaved with observation rows, as in Pumas simulation tables and other NONMEM-style event tables.

The keyword arguments are the same as for read_nca (refer to its docstring for details). The only exception is the dose_time keyword which only read_nca accepts.

In each profile, read_nca_pumas expects exactly one non-missing value in the dose column. This row is the dosing row: its dose and its time (the time for plasma, or the start time for urine) are used to set the dose time for the profile. If the observation (concentration) in the dosing row is missing, only its dosing information is used — the row itself does not produce an observation row in the returned DataFrame.

All other dose-related columns must contain either exactly one non-missing value in the dosing row or the same value across all rows in the profile.

Reading Pumas simulation tables

A DataFrame(simobs(...); include_events = true) table has the shape this function expects: one row per dose administration (with addl already unrolled into individual rows), observation rows that are missing in all dose columns, and a missing concentration on the dose rows.

Such a table follows the NONMEM convention in which a zero on the dose row means "not applicable": duration is 0.0 for everything that is not an infusion, and ii is 0.0 unless the dose is a steady-state dose. read_nca_pumas therefore reads a zero duration or tau on the dose row as missing, as opposed to read_nca for which only missing signals a missing value. An infusion with a zero duration consequently ends up with no duration at all and is rejected when the profile is built.

The route is not part of the event table and has to be supplied as a covariate whose values are the route tokens NCA.jl accepts ("ivbolus", "ivinfusion", "ev"). A covariate named route is picked up automatically, while ii has to be passed for tau:

read_nca_pumas(df; dose = :amt, tau = :ii)

One profile per dose

The dosenum column counts the doses administered at or before each row. It is an ordinary column here with no special meaning, but group = [:dosenum] is an easy way to get one profile per dose. Rows sampled before the first dose have dosenum = 0, they should usually be filtered out before reading.

Without the dosenum grouping, a subject with a single dose keeps its pre-dose samples attached to that dose at a negative time after dose, just as for measured data.

Not every regimen is suitable for NCA

read_nca_pumas does not judge whether the resulting profiles are meaningful. A profile should either cover a true steady-state dosing interval, declared via tau, or a single dose given after full washout. Checking that the simulated regimen satisfies this is up to the user.

Example

julia> using NCA, DataFrames

julia> df = DataFrame(
           id = [1, 1, 1, 2, 2, 2],
           time = [0, 1, 2, 0, 1, 2],
           # the first profile drops the first row, the second doesn't:
           concentration = [missing, 5.0, 3.0, 7.2, 3.4, 1.2],
           amt = [100, missing, missing, 50, missing, missing],
           # a single non-missing value is allowed:
           route = ["ev", missing, missing, "ev", missing, missing],
           # the same value across a profile is also allowed:
           tau = [12, 12, 12, 24, 24, 24],
       );

julia> nca_df = read_nca_pumas(df; dose = :amt);

group_columns

NCA.group_columns — Function
group_columns(df::AbstractDataFrame)::Vector{Symbol}

Return the grouping columns of an AbstractDataFrame that was the output of one of the read_nca variants or of run_nca. These are the columns that appear before the :id and :profile columns, if any.

Both read_nca and run_nca return a DataFrame, so this is useful for recovering the grouping columns of such a result — for example to group, summarise, or join the data downstream without having to track the group names yourself.