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.
Files changed (46) hide show
  1. pkpdutils/__init__.py +73 -0
  2. pkpdutils/console.py +30 -0
  3. pkpdutils/fit/__init__.py +47 -0
  4. pkpdutils/fit/compare.py +149 -0
  5. pkpdutils/fit/engine.py +1266 -0
  6. pkpdutils/fit/frontends.py +193 -0
  7. pkpdutils/fit/model.py +163 -0
  8. pkpdutils/fit/models.py +20 -0
  9. pkpdutils/fit/models_exponential.py +362 -0
  10. pkpdutils/fit/models_linear.py +168 -0
  11. pkpdutils/fit/models_response.py +158 -0
  12. pkpdutils/fit/options.py +159 -0
  13. pkpdutils/fit/proportionality.py +76 -0
  14. pkpdutils/fit/result.py +177 -0
  15. pkpdutils/log.py +79 -0
  16. pkpdutils/nca/__init__.py +45 -0
  17. pkpdutils/nca/auc.py +210 -0
  18. pkpdutils/nca/nca.py +710 -0
  19. pkpdutils/nca/options.py +243 -0
  20. pkpdutils/nca/result.py +58 -0
  21. pkpdutils/nca/steady_state.py +233 -0
  22. pkpdutils/nca/terminal.py +233 -0
  23. pkpdutils/nca/uncertainty.py +606 -0
  24. pkpdutils/plot/__init__.py +33 -0
  25. pkpdutils/plot/fit.py +389 -0
  26. pkpdutils/plot/meta.py +102 -0
  27. pkpdutils/plot/nca.py +216 -0
  28. pkpdutils/plot/parameters.py +124 -0
  29. pkpdutils/plot/ratio.py +131 -0
  30. pkpdutils/plot/style.py +42 -0
  31. pkpdutils/plot/timecourse.py +165 -0
  32. pkpdutils/py.typed +0 -0
  33. pkpdutils/result.py +579 -0
  34. pkpdutils/stats/__init__.py +92 -0
  35. pkpdutils/stats/bioequivalence.py +398 -0
  36. pkpdutils/stats/ddi.py +284 -0
  37. pkpdutils/stats/meta.py +525 -0
  38. pkpdutils/stats/ratio.py +176 -0
  39. pkpdutils/stats/sample.py +590 -0
  40. pkpdutils/stats/tests.py +559 -0
  41. pkpdutils/timecourse.py +1287 -0
  42. pkpdutils/units.py +144 -0
  43. pkpdutils-1.0.0.dist-info/METADATA +104 -0
  44. pkpdutils-1.0.0.dist-info/RECORD +46 -0
  45. pkpdutils-1.0.0.dist-info/WHEEL +4 -0
  46. 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
+ ]
@@ -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)