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.
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 thegroupkeyword. 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 bemissingto leave the route unspecified;run_ncathen reports route-dependent endpoints asmissing, with the reason in the[endpoint]_errcolumn.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 viadose_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 viadose_time.end_time: The end times for urine collection intervals. Will be transformed to time-after-dose given the dose time information passed viadose_time.volume: The urine volume for each collection interval.
Optional columns (Plasma & Urine)
group: Additional grouping columns beyondidto separate profiles when there are multiple profiles per subject (e.g., different treatments, periods, or formulations). Can be given as one:groupor multiple[:groupA, :groupB]symbols.dose_time: The time at which the dose was administered. This will be used to transformtimefor plasma orstart_timeandend_timefor 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 actualdose_timevalues. This will be used to transformnominal_timefor plasma ornominal_start_timeandnominal_end_timefor urine analyses into time-after-dose (TAD) format. If those columns are already in TAD format,nominal_dose_timeshould be zero to avoid further transformation.tau: The dosing interval for steady-state dosing. Must be positive where it is notmissing.blq: Below limit of quantification flag. Boolean values indicating whether observations are below the limit of quantification. Seetransform_nca(theblq_ruleskeyword) 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 oftrueare included. If the column is absent, or all-missing for a profile, that profile's terminal points are selected automatically instead. Seelambdaz_fitfor a worked example.exclusion: Reason string(s) for excluding individual observations from the analysis. Usemissing(or omit the column) for observations to keep, and separate multiple reasons for one observation with newlines.read_ncaderives a booleanexclusion_maskcolumn from it (truewhere a reason is present). Excluded observations are never deleted and their values are left untouched: both columns remain in theread_nca/transform_ncadata frame so the flagged points stay visible (e.g. for plotting), and the observations are skipped by every endpoint when you callrun_nca.
Optional columns (Plasma)
nominal_time: The nominal (planned) times at which observations were made. If not provided, defaults to thetimecolumn. 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 thestart_timecolumn. 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 theend_timecolumn. 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
0Urine 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.