Repository navigation
Expand file tree
/
Copy patharchitecture.qmd
More file actions
224 lines (177 loc) · 13.4 KB
/
Copy patharchitecture.qmd
File metadata and controls
224 lines (177 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
---
title: "Architecture Overview"
---
```{r setup, include=FALSE}
library(PKNCA)
library(dplyr)
conflicted::conflicts_prefer(dplyr::filter, dplyr::select, dplyr::rename, .quiet = TRUE)
```
## Class hierarchy
PKNCA uses four S3 classes: `PKNCAconc` and `PKNCAdose` feed `PKNCAdata`, and `pk.nca()` wraps the result into `PKNCAresults`:
```{mermaid}
classDiagram
class PKNCAconc {
data: data.frame
formula: conc~time|groups
subject: column name
sparse: logical
units: concu/amountu/timeu
exclude / exclude_half.life / include_half.life
lloq, time.nominal
volume, duration (only if given)
}
class PKNCAdose {
data: data.frame
formula: dose~time|groups
route: intravas./extravas.
duration / rate
units: doseu
time.nominal
}
class PKNCAdata {
conc: PKNCAconc
dose: PKNCAdose
intervals: data.frame
impute: method string or column
units: pknca_units_table()
options: named list
group_ref, grpid_cols
}
class PKNCAresults {
result: data.frame incl. PPANMETH
data: PKNCAdata
exclude: column name
}
PKNCAconc --> PKNCAdata
PKNCAdose --> PKNCAdata
PKNCAdata --> PKNCAresults
```
## How a calculation runs
`PKNCAdata()` assembles the inputs and `pk.nca()` does the work. At a high level (function names are from `R/class-PKNCAdata.R` and `R/pk.calc.all.R`):
```{mermaid}
flowchart TD
A["PKNCAdata()"] --> A1["Intervals: given, or generated per group by<br/>choose.auc.intervals() (auto.interval.method)"]
A1 --> A2["set_intervals() / check.interval.specification()<br/>validate the intervals"]
A2 --> A3["Units table: given, or pknca_units_table()"]
A3 --> B["pk.nca()"]
B --> B1["assert_intervals(); convert date-time<br/>times and intervals to numbers"]
B1 --> B2["Overlay the object's options on a<br/>copy of PKNCA.options()"]
B2 --> B3["Expand reference intervals for<br/>secondary parameters"]
B3 --> B4["full_join_PKNCAdata(): split concentration,<br/>dose, and interval data by group"]
B4 --> C["pk.nca.intervals(): one call per group"]
C --> D["pk.nca.interval(): one call per interval;<br/>dense and sparse parameters in one pass"]
D --> E["Gather results (dense, then sparse)"]
E --> F["pk_nca_secondary(): parameters that<br/>read a reference interval"]
F --> G["PKNCAresults()"]
```
Inside `pk.nca.interval()`, the requested parameters are expanded with their `depends`, put in dependency order, and calculated one at a time. For each parameter, the arguments of its calculation function are filled from the interval data, earlier results, constants, or the options (see [formalsmap](#formalsmap)); points named by `include_half.life`/`exclude_half.life` are applied before `half.life` is calculated; and the result's `exclude` and `"method"` attributes become the `exclude` and `PPANMETH` columns. A function that returns a data frame contributes one result row per column. The calculation functions themselves never see a `PKNCAdata` object, so they can be called directly (see [Utility Functions](../user/utilities.qmd)).
## The interval column system
The parameter registry is the core extensibility mechanism. Every NCA parameter is registered as an **interval column** via `add.interval.col()`.
```{r}
# Inspect registration for auclast
cols <- get.interval.cols()
str(cols[["auclast"]])
```
Each entry contains:
| Field | Purpose |
|---|---|
| `FUN` | Name of the function that computes this parameter (character string, or `NA` for parameters populated by another function's multi-column result or calculated only by `FUN_sparse`) |
| `FUN_sparse` | Name of a sparse-PK estimator, or `NA` (≥ 0.13.0) |
| `values` | Allowed values in the interval column (typically `c(FALSE, TRUE)`) |
| `unit_type` | Unit category used for unit assignment and conversion |
| `pretty_name` | Human-readable name used in summary tables |
| `desc` | Human-readable description (40 characters or fewer for SDTM; longer warns) |
| `formalsmap` | Maps function arguments to interval/group/options data, other parameters, constants, or reference-interval values |
| `formalsmap_sparse` | The `formalsmap` for `FUN_sparse` (≥ 0.13.0) |
| `depends` | Character vector of parameters that must be computed first |
| `datatype` | Calculation type; only `"interval"` is currently supported |
| `pptestcd_cdisc` | CDISC PPTESTCD code: a string, a route-keyed list, or a dense/sparse list (≥ 0.13.0) |
| `pptest_cdisc` | CDISC PPTEST label, in the same forms (≥ 0.13.0) |
| `formula`, `formula_note` | LaTeX formula and note for documentation; `formula_note` is the `PPANMETH` fallback (≥ 0.13.0) |
| `tier`, `selection` | Classification for automatic interval selection (≥ 0.13.0) |
The registry has no `sparse` field (≥ 0.13.0): `add.interval.col(sparse = TRUE)` is retired and is an error. Whether a parameter needs sparse data is derived from the registration (a `FUN_sparse` and no `FUN`) and reported by `pknca_parameter_table()`, together with each parameter's concept, tier, and route and dosing contexts. After `pk.nca()` first uses an entry, it can also carry internal cached fields (`requires_*` and `arg_spec`) that are not part of the registration.
## formalsmap
`formalsmap` is how PKNCA passes the right data to each parameter function. Arguments can be sourced from these places:
| Source type | Example | What it provides |
|---|---|---|
| Interval data | `conc`, `time`, `dose`, `time.dose`, `route` | Data for the current interval (times relative to the interval start) |
| Group data | `conc.group`, `time.group` | Data for the whole group |
| Sparse data (≥ 0.13.0) | `conc.sparse`, `time.sparse`, `subject` | The pooled individual samples (`NULL` for dense data) |
| Other parameters | `auc = "auclast"` | A previously computed NCA result |
| Interval bounds | `start`, `end` | The interval start/end times |
| Options | `options` | The full analysis options list |
| Constants (≥ 0.13.0) | `auc.type = I("AUCall")` | The value itself |
| Reference interval (≥ 0.13.0) | `auc = pknca_ref("auclast")` | A parameter's value in the reference interval; makes the parameter secondary |
Individual option names (e.g. `auc.method`) cannot be mapped through `formalsmap`; only the whole `options` list can, and parameter functions read specific settings from it via `PKNCA.choose.option()`.
## Writing a custom parameter
Registering a new parameter is a single `add.interval.col()` call that names the computing function; PKNCA then derives execution order from `depends`, assigns units from `unit_type`, and collects results automatically. A minimal toy registration:
```{r}
# Toy illustration: ratio of two built-in parameters
pk.calc.demo.ratio <- function(cmax, cmin) {
cmax / cmin
}
add.interval.col(
name = "demo.ratio",
FUN = "pk.calc.demo.ratio", # must be a character string, not the function object
unit_type = "unitless",
pretty_name = "Demo ratio (Cmax/Cmin)",
desc = "Toy example: Cmax divided by Cmin",
depends = c("cmax", "cmin")
)
# The registry now contains it alongside the built-in parameters
str(get.interval.cols()[["demo.ratio"]])
```
For the full walkthrough — parameters computed from raw concentration-time data, constants, multi-output functions, parameters that read a reference interval, sparse-only parameters, and custom summary statistics — see the [Writing Custom Parameters chapter](../user/custom-parameters.qmd) and the package vignette [Writing PKNCA Parameter Functions](https://humanpred.github.io/pknca/articles/v80-writing-parameter-functions.html).
## Options system
PKNCA's options are stored in a package-level environment; `PKNCA.options()` returns them as a named list. It is accessed in several ways:
```{r}
# 1. Read all options
PKNCA.options()
# 2. Read a specific option
PKNCA.options("min.hl.points")
# 3. Set globally — save the old value first so you can restore it
old_val <- PKNCA.options("min.hl.points")[[1]]
PKNCA.options(min.hl.points = 4)
PKNCA.options("min.hl.points")
# 4. Restore by passing the saved value as a named argument
PKNCA.options(min.hl.points = old_val)
PKNCA.options("min.hl.points")
```
> Prefer passing `options = list(...)` to `PKNCAdata()` over mutating global options. Global changes persist across analyses in the same session. At the start of each run, `pk.nca()` overlays the object's options on a copy of the global options and uses that copy for the calculations; the global options themselves are not modified. `PKNCA_options_defaults()` (≥ 0.13.0) returns the package defaults without resetting anything.
## Sparse PK
Sparse PK (one or two samples per subject, common in preclinical or pediatric studies) shares the dense code path (≥ 0.13.0):
- Set `sparse = TRUE` in `PKNCAconc()`.
- Each group reaches `pk.nca.interval()` with two representations of the same samples: the arithmetic-mean profile (as `conc`/`time`) and the pooled individual samples (as `conc.sparse`/`time.sparse`, with `subject`). Dense and sparse parameters are calculated in one pass.
- A parameter with a `FUN_sparse` (such as `auclast`, whose sparse estimator also returns `auclast_se` and `auclast_df`) is calculated from the pooled samples; one without falls back to `FUN` on the mean profile (half-life, for example). A parameter with `FUN = NA` and a `FUN_sparse` is sparse-only, and requesting it for dense data is an error.
- `pknca_parameter_table()$sparse` reports which parameters need sparse data.
For the full workflow, see the [Sparse NCA chapter](../user/sparse.qmd) and the vignette [Sparse NCA Calculations](https://humanpred.github.io/pknca/articles/v04-sparse.html).
## Conditions and messages
Errors, warnings, and messages are signaled with `rlang` and carry classes named `pknca_error_*`, `pknca_warning_*`, and `pknca_message_*` (≥ 0.13.0), so code can catch them by class rather than by message text. Several older classes were renamed in 0.13.0 (for example, `pknca_conc_none` is now `pknca_warning_no_concentration`). Input validation uses `checkmate`. With `verbose = TRUE`, `pk.nca()` reports one "Starting PK NCA calculations." message (`pknca_message_pk_start`).
```{r}
# Catch a PKNCA error by its class
tryCatch(
add.interval.col(name = "x_ref", FUN = "pk.calc.cmax", unit_type = "conc",
pretty_name = "x", desc = "Reserved name"),
pknca_error_param_name_reserved = function(e) class(e)[1]
)
```
## Key functions
All of these are exported except where marked internal:
| Function | Purpose |
|---|---|
| `get.interval.cols()` | Return full parameter registry |
| `pknca_parameter_table()` | Classification of every parameter (concept, tier, sparse, secondary, contexts) |
| `get.parameter.deps()` | Parameters calculated from a parameter, or with `recursive = TRUE` what it is calculated from |
| `PKNCA:::sort_interval_cols()` | Topological sort → execution order (internal, unexported) |
| `assert_intervals()` | Validate an interval specification against a `PKNCAdata` object |
| `pk.nca.interval()` | Compute all parameters for one interval/group; takes `conc.sparse`, `time.sparse`, `conc.sparse.group`, and `time.sparse.group` for sparse data (its `sparse` argument was removed in 0.13.0) |
| `pknca_units_table()` | Build unit assignment/conversion table |
| `PKNCA_impute_method_*()` | Built-in imputation methods (listed by `pknca_impute_methods()`) |
| `pk.calc.auc.last()` | Core AUC calculation; returns a `"method"` attribute |
| `pk.calc.half.life()` | λz regression with best-fit selection |
---
> This overview covers the surface. The business rules (`002-pk.business.rules.R`) and the validation framework ([`v60-PKNCA-validation.Rmd`](https://humanpred.github.io/pknca/articles/v60-PKNCA-validation.html)) in the PKNCA sources are richer topics worth exploring further.
---
::: {.callout-note icon=false appearance="minimal"}
**pkgdown reference:** [PKNCAconc()](https://humanpred.github.io/pknca/reference/PKNCAconc.html) · [PKNCAdose()](https://humanpred.github.io/pknca/reference/PKNCAdose.html) · [PKNCAdata()](https://humanpred.github.io/pknca/reference/PKNCAdata.html) · [pk.nca()](https://humanpred.github.io/pknca/reference/pk.nca.html) · [add.interval.col()](https://humanpred.github.io/pknca/reference/add.interval.col.html) · [get.interval.cols()](https://humanpred.github.io/pknca/reference/get.interval.cols.html) · [PKNCA.options()](https://humanpred.github.io/pknca/reference/PKNCA.options.html) · [pknca_units_table()](https://humanpred.github.io/pknca/reference/pknca_units_table.html) · [pk.nca.interval()](https://humanpred.github.io/pknca/reference/pk.nca.interval.html) · [PKNCA.choose.option()](https://humanpred.github.io/pknca/reference/PKNCA.choose.option.html) · [pk.calc.half.life()](https://humanpred.github.io/pknca/reference/pk.calc.half.life.html) · [pknca_parameter_table()](https://humanpred.github.io/pknca/reference/pknca_parameter_table.html) · [get.parameter.deps()](https://humanpred.github.io/pknca/reference/get.parameter.deps.html) · [assert_intervals()](https://humanpred.github.io/pknca/reference/assert_intervals.html) · [choose.auc.intervals()](https://humanpred.github.io/pknca/reference/choose.auc.intervals.html) · [check.interval.specification()](https://humanpred.github.io/pknca/reference/check.interval.specification.html) · [PKNCA_options_defaults()](https://humanpred.github.io/pknca/reference/PKNCA_options_defaults.html) · [pknca_impute_methods()](https://humanpred.github.io/pknca/reference/pknca_impute_methods.html)
:::