Migrating from NCA v2

NCA v3, which ships with Pumas 2.9, is a rewrite of the package rather than an incremental release. Code written against NCA v2 (Pumas 2.8 and earlier, with plots from NCAUtilities) will not run unchanged. This page explains the changes and closes with a list of the individual renames and removals.

Migrating with a coding agent

The mechanical part of a migration is well suited to coding agents. For that purpose, you can point your agent at the skill file below, which covers the most common problems and their fixes.

Design changes

A v2 analysis and its v3 counterpart look like this:

# v2
using NCA, NCAUtilities
pop = read_nca(df; observations = :conc, amt = :amt, ii = :ii, ss = :ss, llq = 0.1)
# pop isa NCAPopulation
report = run_nca(pop; parameters = [:cmax, :aucinf_obs], sigdigits = 3)
# report isa NCAReport
results = report.reportdf
# results isa DataFrame
auc = NCA.auc(pop; interval = (0, 12))
observations_vs_time(pop)

# v3
using NCA
nca_data = read_nca(df; concentration = :conc, dose = :amt, tau = :tau)
# nca_data isa DataFrame
nca_data = transform_nca(nca_data; llq = 0.1)
results = run_nca(nca_data, [:cmax, :aucinf_obs, :auc_partial => (; interval = (0, 12)) => :auc_0_12])
# results isa DataFrame
observations_vs_time(nca_data)

Profiles instead of subjects

NCA v2 was organised around NCASubject, an object holding all observations of one subject, with NCAPopulation as a vector of them. Single-dose and multi-dose subjects were represented with different data structures, which complicated the analysis as well as making tables, listings and figures.

NCA v3 replaces the different subject types with one concept, the profile: the observations belonging to a single dosing event. A subject that received three doses contributes three profiles. Multiple dosing is handled by one or more grouping columns that tell the profiles of a given subject apart. The user does not interact with a profile as a special type; a profile is simply the contiguous rows of the input DataFrame that belong to one dosing event.

Time in a profile is now always understood as time after dose, and absolute times are normalised automatically when the dose time is given. This makes the computations more robust and the inputs and outputs of NCA easier to understand.

DataFrames in, DataFrames out

Inputs and outputs of NCA are now plain DataFrames. read_nca returns one with a fixed layout (grouping columns, then id, then the profile index, then the observation and dose columns), and run_nca takes such a frame and returns one row per profile with one column per endpoint.

The NCAReport wrapper from v2 is gone. Reports are better served by Quarto notebooks with the native Julia engine, which combine the result tables and plots with far more flexibility.

Tidy input data by default

v2 read_nca accepted several layouts at once and guessed column names from a list of aliases and partial matches. NCA v3 has two readers with one layout each:

  • read_nca is the default and preferred reader. It reads tidy data, where every row is an observation and the dose information (dose, route, tau, duration, dose_time) is repeated on every row of a profile.
  • read_nca_pumas is a convenience function for event-style tables, which appear mainly as the result of Pumas simulations, where only one row per profile carries the dose information. Use it only when the data already has this shape.

Column names are now matched exactly and case-insensitively against the v3 keyword names (concentration, dose, tau, ...), which reduces the potential for errors.

Steady state through tau and assumed_tau

v2 expressed steady state with the pair ii and ss, and the same ii value meant different things depending on the flag. NCA v3 has two columns with one meaning each. A non-missing tau marks a profile as measured at steady state and unlocks the observed interval endpoints (ctau, cavg, auctau_obs, auctau_pred, fluctuation, swing). A non-missing assumed_tau marks a single-dose profile with a hypothetical interval and unlocks the predicted steady-state endpoints (cmaxss, cminss, cavgss, accumulation_index). A profile may have only one of the two. Both are input columns of the read step, so they can vary per profile.

Endpoints are requested through run_nca

Individual endpoint functions such as NCA.auc(pop; interval = (0, 12)) are gone in favour of the central run_nca mechanism. Computing all requested endpoints in one pass lets shared intermediate results, such as the λz fit, be computed once and reused, which is more efficient. All endpoints are requested through the second positional argument of run_nca, as symbols, optionally with options and an output name:

run_nca(nca_data, [
    :cmax,
    :auclast => (; method = :linlog) => :auclast_log,
    :auc_partial => (; interval = (0, 12)) => :auc_0_12,
    :lambdaz => :kel,
])

Endpoint names were made consistent: observed and predicted variants end in _obs and _pred, dose-normalised variants in _dn, percentages in _percent, and the λz regression statistics all start with lambdaz. The full mapping from v2 names is in the migration skill, and the current names are listed in the Endpoint Reference.

All endpoints were rewritten to agree with industry standards for performing NCA, so individual numerical results may differ from v2 where v2 deviated from those standards. The renames and removals at the end of this page therefore cover only API changes.

Failures are reported in the results

When an endpoint could not be computed for a profile, v2 logged a warning and moved on. Warnings are easily missed, especially in automated or agent-based workflows. NCA v3 therefore puts missing in any row where an endpoint could not be computed and adds an <endpoint>_err column with the reason for that row. These columns can then be analysed programmatically or by agents.

NCA v2 also warned when input data did not fully conform to the expected format. NCA v3 either succeeds or throws an error with a clear message.

Transformations keep every row

BLQ handling, LLQ truncation and sparse sampling moved from read_nca keywords into transform_nca, a separate step between reading and analysis. When it modifies concentrations, the original values stay in concentration_before_blq. Exclusions of individual observations work the same way: an exclusion column with reason strings is turned into a boolean exclusion_mask, and a lambdaz_mask column selects the points of the terminal regression. In all cases the rows remain in the frame, which keeps an audit trail and lets the plots show gaps in the data correctly.

Plots and tables live in NCA

NCAUtilities was merged into NCA, so observations_vs_time, subject_fits, parameters_vs_group, the dose linearity plots and geomean_cv_table come with using NCA. The observation plots take the read_nca output, the endpoint plots take the run_nca result.

Pumas interop via DataFrames

Pumas 2.9 no longer depends on NCA, so using Pumas does not bring NCA into scope and NCA has to be added to the project and loaded. The @nca block in models and the conversions of Subject and Population to NCASubject and NCAPopulation are removed. Instead, a simulation is turned into a table and read like any other event-style dataset:

using Pumas, NCA, DataFrames

df = DataFrame(simobs(model, population, params); include_events = true)
df.route .= "ivbolus"
nca_data = read_nca_pumas(df; concentration = :dv, dose = :amt, tau = :ii, group = [:dosenum])
results = run_nca(nca_data, [:cmax, :auclast, :half_life])

route is no longer part of the Pumas dose representation. DosageRegimen(...; route = NCA.IVBolus) warns and ignores the keyword; carry the route as a covariate or add the string column to the table.

Renames and removals

read_nca and read_nca_pumas

  • Keyword renames: observations to concentration, amt to dose, ii to tau or assumed_tau, timeu, concu, amtu and volumeu to time_unit, concentration_unit, dose_unit and volume_unit.
  • Units are all or nothing: when one unit keyword is given, every dose-related column needs one, and numeric options such as llq or interval then need units too.
  • Unitful support is an extension, so Unitful has to be loaded explicitly and NCA.Unitful no longer exists. DynamicQuantities is supported through a second extension.
  • route is required. "ivbolus" and "ivinfusion" are the preferred spellings; "iv" and "inf" still work. NullRoute is deprecated in favour of missing.
  • read_nca takes dose_time for absolute time columns; without it, time is time after dose. Missing times are an error.
  • blq must be a Bool column. llq, concblq, sparse, missingconc, missingvolume and verbose are no longer read_nca keywords.
  • Group column names that NCA uses itself (dose, tau, route, ...) are reserved.
  • Both readers reject a non-positive tau and a non-positive infusion duration.

run_nca and endpoints

  • Endpoints are the second positional argument; parameters, sigdigits and the report metadata keywords are gone.
  • Renamed endpoints: thalf to half_life, lambdazr to lambdazcorr, auctau to auctau_obs, aumctau to aumctau_obs, aumc to aumcinf_obs, mrt to mrtinf_obs, cl to cl_obs or cl_f_obs, vz to vz_obs or vz_f_obs, vss to vss_obs, accumulationindex to accumulation_index, auc_extrap_percent, aumc_extrap_percent, auc_back_extrap_percent and aurc_extrap_percent to their _obs variants, auc_stderr to auclast_stderr, and aurc to aurclast. The old names still work with a deprecation warning.
  • Endpoints that take options (auc_partial, cmax_interval, tmax_interval, the method-aware AUC endpoints, lambdaz_fit, cpred) require an output name when options are given, and the name may not be another endpoint's name.
  • dose, route, tau and assumed_tau are columns of the result, not endpoints.
  • run_status is removed; use the _err columns.
  • concthreshold of the λz fit is removed; the BLQ system covers it.
  • New: lambdaz_diagnostics with per-point regression diagnostics, together with format_diagnostics and exclude_lambdaz (experimental).

summarize

  • Takes the run_nca result. skipmissings is removed and missing values are always skipped. numsamples and nonmissing are always included. Integer indices for parameters and stratify_by are no longer accepted, and parameters defaults to every non-grouping column, including renamed and partial AUC endpoints.

Plots and tables

  • Plot functions take the read_nca output or the run_nca result instead of a population or report. limit may not be combined with rows/columns, facet = (; combinelabels = true) is removed, and axis linking is set with the AlgebraOfGraphics options facet = (; linkxaxes, linkyaxes).
  • geomean_cv_table is NCA.geomean_cv_table and takes the run_nca result.

Other

  • NCA.superposition takes the read_nca frame and the keywords assumed_tau, n_doses, maintenance_dose and loading_dose instead of ii, ndoses and amt. steadystatetol is gone, and n_doses = Inf is capped by max_auto_doses.
  • Dose linearity models take the run_nca result.
  • Removed: NCASubject, NCAPopulation, NCADose, NCAReport, report, showunits, NCA.cleanblq, read_nca on a file path, and the two-frame read_nca(df_obs, df_dose) form.
  • Joint plasma and urine datasets can no longer be analysed in one frame, since times may not be missing; analyse them separately.