pkpdutils 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pkpdutils/__init__.py +73 -0
- pkpdutils/console.py +30 -0
- pkpdutils/fit/__init__.py +47 -0
- pkpdutils/fit/compare.py +149 -0
- pkpdutils/fit/engine.py +1266 -0
- pkpdutils/fit/frontends.py +193 -0
- pkpdutils/fit/model.py +163 -0
- pkpdutils/fit/models.py +20 -0
- pkpdutils/fit/models_exponential.py +362 -0
- pkpdutils/fit/models_linear.py +168 -0
- pkpdutils/fit/models_response.py +158 -0
- pkpdutils/fit/options.py +159 -0
- pkpdutils/fit/proportionality.py +76 -0
- pkpdutils/fit/result.py +177 -0
- pkpdutils/log.py +79 -0
- pkpdutils/nca/__init__.py +45 -0
- pkpdutils/nca/auc.py +210 -0
- pkpdutils/nca/nca.py +710 -0
- pkpdutils/nca/options.py +243 -0
- pkpdutils/nca/result.py +58 -0
- pkpdutils/nca/steady_state.py +233 -0
- pkpdutils/nca/terminal.py +233 -0
- pkpdutils/nca/uncertainty.py +606 -0
- pkpdutils/plot/__init__.py +33 -0
- pkpdutils/plot/fit.py +389 -0
- pkpdutils/plot/meta.py +102 -0
- pkpdutils/plot/nca.py +216 -0
- pkpdutils/plot/parameters.py +124 -0
- pkpdutils/plot/ratio.py +131 -0
- pkpdutils/plot/style.py +42 -0
- pkpdutils/plot/timecourse.py +165 -0
- pkpdutils/py.typed +0 -0
- pkpdutils/result.py +579 -0
- pkpdutils/stats/__init__.py +92 -0
- pkpdutils/stats/bioequivalence.py +398 -0
- pkpdutils/stats/ddi.py +284 -0
- pkpdutils/stats/meta.py +525 -0
- pkpdutils/stats/ratio.py +176 -0
- pkpdutils/stats/sample.py +590 -0
- pkpdutils/stats/tests.py +559 -0
- pkpdutils/timecourse.py +1287 -0
- pkpdutils/units.py +144 -0
- pkpdutils-1.0.0.dist-info/METADATA +104 -0
- pkpdutils-1.0.0.dist-info/RECORD +46 -0
- pkpdutils-1.0.0.dist-info/WHEEL +4 -0
- pkpdutils-1.0.0.dist-info/licenses/LICENSE +7 -0
pkpdutils/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""pkpdutils: pharmacokinetic and pharmacodynamic analysis of timecourses and parameters.
|
|
2
|
+
|
|
3
|
+
The data model is `Timecourse` (one curve) and `Timecourses` (a batch as an
|
|
4
|
+
xarray dataset), see `pkpdutils.timecourse`; units are pint quantities of the
|
|
5
|
+
shared registry `ureg`, see `pkpdutils.units`. The analyses are
|
|
6
|
+
`pkpdutils.nca` (non-compartmental analysis) and `pkpdutils.fit` (curve
|
|
7
|
+
fitting), both returning a `pkpdutils.result.ParameterResult`. `pkpdutils.stats`
|
|
8
|
+
holds the statistics on parameters (tests, ratios, bioequivalence,
|
|
9
|
+
drug-drug interactions, meta-analysis).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pkpdutils.fit import (
|
|
13
|
+
FitOptions,
|
|
14
|
+
FitResult,
|
|
15
|
+
compare_models,
|
|
16
|
+
fit,
|
|
17
|
+
fit_table,
|
|
18
|
+
fit_timecourse,
|
|
19
|
+
fit_timecourses,
|
|
20
|
+
proportionality_test,
|
|
21
|
+
)
|
|
22
|
+
from pkpdutils.nca import (
|
|
23
|
+
NCAOptions,
|
|
24
|
+
NCAResult,
|
|
25
|
+
TerminalPhase,
|
|
26
|
+
nca,
|
|
27
|
+
nca_single,
|
|
28
|
+
partial_auc,
|
|
29
|
+
)
|
|
30
|
+
from pkpdutils.stats import (
|
|
31
|
+
ParameterSample,
|
|
32
|
+
bioequivalence,
|
|
33
|
+
compare,
|
|
34
|
+
ddi_classification,
|
|
35
|
+
meta_analysis,
|
|
36
|
+
ratio,
|
|
37
|
+
)
|
|
38
|
+
from pkpdutils.timecourse import Dose, DosingRegimen, Route, Timecourse, Timecourses
|
|
39
|
+
from pkpdutils.units import Q_, Quantity, ureg
|
|
40
|
+
|
|
41
|
+
__version__ = "1.0.0"
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"Q_",
|
|
45
|
+
"Dose",
|
|
46
|
+
"DosingRegimen",
|
|
47
|
+
"FitOptions",
|
|
48
|
+
"FitResult",
|
|
49
|
+
"NCAOptions",
|
|
50
|
+
"NCAResult",
|
|
51
|
+
"ParameterSample",
|
|
52
|
+
"Quantity",
|
|
53
|
+
"Route",
|
|
54
|
+
"TerminalPhase",
|
|
55
|
+
"Timecourse",
|
|
56
|
+
"Timecourses",
|
|
57
|
+
"__version__",
|
|
58
|
+
"bioequivalence",
|
|
59
|
+
"compare",
|
|
60
|
+
"compare_models",
|
|
61
|
+
"ddi_classification",
|
|
62
|
+
"fit",
|
|
63
|
+
"fit_table",
|
|
64
|
+
"fit_timecourse",
|
|
65
|
+
"fit_timecourses",
|
|
66
|
+
"meta_analysis",
|
|
67
|
+
"nca",
|
|
68
|
+
"nca_single",
|
|
69
|
+
"partial_auc",
|
|
70
|
+
"proportionality_test",
|
|
71
|
+
"ratio",
|
|
72
|
+
"ureg",
|
|
73
|
+
]
|
pkpdutils/console.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Shared rich console.
|
|
2
|
+
|
|
3
|
+
The console is used for the output of scripts and examples; library code logs
|
|
4
|
+
instead of printing, see `pkpdutils.log`.
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pkpdutils.console import console
|
|
8
|
+
|
|
9
|
+
console.print(result)
|
|
10
|
+
console.rule("Section", style="white")
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Importing this module has no side effects on the interpreter. To get rich
|
|
14
|
+
representations in an interactive session, install them explicitly with
|
|
15
|
+
`rich.pretty.install()`.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.theme import Theme
|
|
20
|
+
|
|
21
|
+
custom_theme = Theme(
|
|
22
|
+
{
|
|
23
|
+
"success": "green",
|
|
24
|
+
"info": "blue",
|
|
25
|
+
"warning": "orange3",
|
|
26
|
+
"error": "red",
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console(theme=custom_theme)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Curve fitting of timecourses and parameters.
|
|
2
|
+
|
|
3
|
+
`fit`, `fit_timecourse`, `fit_timecourses` and `fit_table` fit a `Model` from
|
|
4
|
+
`pkpdutils.fit.models` to data with `scipy.optimize.least_squares`, see
|
|
5
|
+
`docs/fitting.md`; `FitOptions` selects the scale, the weighting and the
|
|
6
|
+
multi-start and bootstrap settings, `FitResult` holds the parameters.
|
|
7
|
+
`compare_models` fits several models to the same data and ranks them by the
|
|
8
|
+
corrected Akaike information criterion. `proportionality_test` applies the
|
|
9
|
+
confidence interval criterion of dose proportionality to a `Power` fit.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pkpdutils.fit.compare import ModelComparison, compare_models
|
|
13
|
+
from pkpdutils.fit.engine import RowFit, build_result, fit, fit_row, fit_rows
|
|
14
|
+
from pkpdutils.fit.frontends import fit_table, fit_timecourse, fit_timecourses
|
|
15
|
+
from pkpdutils.fit.model import Model, ModelParameter, parameter_unit_expression
|
|
16
|
+
from pkpdutils.fit.options import (
|
|
17
|
+
FitFlag,
|
|
18
|
+
FitOptions,
|
|
19
|
+
ParameterScale,
|
|
20
|
+
Weighting,
|
|
21
|
+
decode_fit_flags,
|
|
22
|
+
)
|
|
23
|
+
from pkpdutils.fit.proportionality import proportionality_test
|
|
24
|
+
from pkpdutils.fit.result import FitResult
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"FitFlag",
|
|
28
|
+
"FitOptions",
|
|
29
|
+
"FitResult",
|
|
30
|
+
"Model",
|
|
31
|
+
"ModelComparison",
|
|
32
|
+
"ModelParameter",
|
|
33
|
+
"ParameterScale",
|
|
34
|
+
"RowFit",
|
|
35
|
+
"Weighting",
|
|
36
|
+
"build_result",
|
|
37
|
+
"compare_models",
|
|
38
|
+
"decode_fit_flags",
|
|
39
|
+
"fit",
|
|
40
|
+
"fit_row",
|
|
41
|
+
"fit_rows",
|
|
42
|
+
"fit_table",
|
|
43
|
+
"fit_timecourse",
|
|
44
|
+
"fit_timecourses",
|
|
45
|
+
"parameter_unit_expression",
|
|
46
|
+
"proportionality_test",
|
|
47
|
+
]
|
pkpdutils/fit/compare.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Comparison of models by the corrected Akaike information criterion.
|
|
2
|
+
|
|
3
|
+
`compare_models` fits every model to the same data and ranks them per sample
|
|
4
|
+
by AICc (Burnham & Anderson 2002, ch. 2): `Delta_i = AICc_i - min_j AICc_j`
|
|
5
|
+
and the Akaike weight `w_i = exp(-Delta_i / 2) / sum_j exp(-Delta_j / 2)`, the
|
|
6
|
+
probability that model `i` is the best of the set given the candidates
|
|
7
|
+
considered. AICc counts the residual variance as an estimated parameter,
|
|
8
|
+
`K = k + 1` (Burnham & Anderson 2002, sec. 2.2, 6.9.6); a model whose AICc is
|
|
9
|
+
`NaN` (too few points for its number of parameters, `n - K - 1 <= 0`) gets
|
|
10
|
+
weight 0 and is never picked as `best`; when every model of a sample is
|
|
11
|
+
`NaN`, `best` is the empty string.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pandas as pd
|
|
20
|
+
import xarray as xr
|
|
21
|
+
|
|
22
|
+
from pkpdutils.fit.engine import fit
|
|
23
|
+
from pkpdutils.fit.model import Model
|
|
24
|
+
from pkpdutils.fit.options import FitOptions
|
|
25
|
+
from pkpdutils.fit.result import FitResult
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ModelComparison:
|
|
30
|
+
"""The fits of several models and their ranking by AICc.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
results: model name to its `FitResult`.
|
|
34
|
+
table: one row per sample and model, with the sample dims, `model`,
|
|
35
|
+
`n_parameters`, `aicc`, `delta_aicc`, `akaike_weight` and `best`.
|
|
36
|
+
best: name of the best model per sample, over the sample dimensions
|
|
37
|
+
(empty string for a sample where no model fitted).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
results: dict[str, FitResult]
|
|
41
|
+
table: pd.DataFrame
|
|
42
|
+
best: xr.DataArray
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def compare_models(
|
|
46
|
+
models: Sequence[Model],
|
|
47
|
+
x: Any,
|
|
48
|
+
y: Any,
|
|
49
|
+
*,
|
|
50
|
+
sd: Any | None = None,
|
|
51
|
+
options: FitOptions | None = None,
|
|
52
|
+
x_unit: str = "dimensionless",
|
|
53
|
+
y_unit: str = "dimensionless",
|
|
54
|
+
dims: Sequence[str] | None = None,
|
|
55
|
+
coords: dict[str, Any] | None = None,
|
|
56
|
+
) -> ModelComparison:
|
|
57
|
+
"""Fit every model to the same data and rank them per sample by AICc.
|
|
58
|
+
|
|
59
|
+
`Delta_i = AICc_i - min_j AICc_j` and the Akaike weight
|
|
60
|
+
`w_i = exp(-Delta_i / 2) / sum_j exp(-Delta_j / 2)` (Burnham & Anderson
|
|
61
|
+
2002, ch. 2) are computed independently for every sample, so a different
|
|
62
|
+
model can be the best fit of different samples of a batch.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
models: the candidate models, with distinct `name`s.
|
|
66
|
+
x: independent variable, as for `fit`.
|
|
67
|
+
y: dependent variable, as for `fit`.
|
|
68
|
+
sd: standard deviations, as for `fit`.
|
|
69
|
+
options: fit options shared by every model.
|
|
70
|
+
x_unit: unit of `x`.
|
|
71
|
+
y_unit: unit of `y`.
|
|
72
|
+
dims: sample dimension names for a 2-D `y`.
|
|
73
|
+
coords: coordinates of the sample dimensions.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The comparison.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: if two models share a `name`.
|
|
80
|
+
"""
|
|
81
|
+
names = [m.name for m in models]
|
|
82
|
+
if len(set(names)) != len(names):
|
|
83
|
+
raise ValueError(f"Model names must be distinct: {names}")
|
|
84
|
+
results = {
|
|
85
|
+
m.name: fit(
|
|
86
|
+
m,
|
|
87
|
+
x,
|
|
88
|
+
y,
|
|
89
|
+
sd=sd,
|
|
90
|
+
options=options,
|
|
91
|
+
x_unit=x_unit,
|
|
92
|
+
y_unit=y_unit,
|
|
93
|
+
dims=dims,
|
|
94
|
+
coords=coords,
|
|
95
|
+
)
|
|
96
|
+
for m in models
|
|
97
|
+
}
|
|
98
|
+
first = next(iter(results.values()))
|
|
99
|
+
sample_dims = first.sample_dims
|
|
100
|
+
aicc = np.stack(
|
|
101
|
+
[results[name]["aicc"].to_numpy() for name in names], axis=-1
|
|
102
|
+
) # (*sample_shape, n_models)
|
|
103
|
+
with np.errstate(invalid="ignore"):
|
|
104
|
+
finite = np.isfinite(aicc)
|
|
105
|
+
best_value = np.nanmin(np.where(finite, aicc, np.inf), axis=-1)
|
|
106
|
+
delta = np.where(finite, aicc - best_value[..., None], np.nan)
|
|
107
|
+
weight = np.where(finite, np.exp(-0.5 * np.where(finite, delta, 0.0)), 0.0)
|
|
108
|
+
total = weight.sum(axis=-1, keepdims=True)
|
|
109
|
+
weight = np.where(total > 0, weight / np.where(total > 0, total, 1.0), 0.0)
|
|
110
|
+
has_any = finite.any(axis=-1)
|
|
111
|
+
best_index = np.where(has_any, np.argmax(weight, axis=-1), len(names))
|
|
112
|
+
best_names = np.array([*names, ""], dtype=object)[best_index]
|
|
113
|
+
coords_sample = {d: first.ds[d] for d in sample_dims if d in first.ds.coords}
|
|
114
|
+
best = xr.DataArray(
|
|
115
|
+
best_names, dims=sample_dims, coords=coords_sample, name="best_model"
|
|
116
|
+
)
|
|
117
|
+
rows = []
|
|
118
|
+
for index in np.ndindex(*aicc.shape[:-1]):
|
|
119
|
+
labels = {
|
|
120
|
+
d: (first.ds[d].values[i] if d in first.ds.coords else i)
|
|
121
|
+
for d, i in zip(sample_dims, index, strict=True)
|
|
122
|
+
}
|
|
123
|
+
for j, name in enumerate(names):
|
|
124
|
+
rows.append(
|
|
125
|
+
{
|
|
126
|
+
**labels,
|
|
127
|
+
"model": name,
|
|
128
|
+
"n_parameters": int(
|
|
129
|
+
results[name]["n_parameters"].to_numpy()[index]
|
|
130
|
+
),
|
|
131
|
+
"aicc": float(aicc[(*index, j)]),
|
|
132
|
+
"delta_aicc": float(delta[(*index, j)]),
|
|
133
|
+
"akaike_weight": float(weight[(*index, j)]),
|
|
134
|
+
"best": bool(has_any[index] and best_index[index] == j),
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
table = pd.DataFrame(
|
|
138
|
+
rows,
|
|
139
|
+
columns=[
|
|
140
|
+
*sample_dims,
|
|
141
|
+
"model",
|
|
142
|
+
"n_parameters",
|
|
143
|
+
"aicc",
|
|
144
|
+
"delta_aicc",
|
|
145
|
+
"akaike_weight",
|
|
146
|
+
"best",
|
|
147
|
+
],
|
|
148
|
+
)
|
|
149
|
+
return ModelComparison(results=results, table=table, best=best)
|