pystatsbio 1.0.0__tar.gz

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 (146) hide show
  1. pystatsbio-1.0.0/.github/workflows/publish.yml +45 -0
  2. pystatsbio-1.0.0/.gitignore +31 -0
  3. pystatsbio-1.0.0/CHANGELOG.md +34 -0
  4. pystatsbio-1.0.0/CLAUDE.md +190 -0
  5. pystatsbio-1.0.0/LICENSE +21 -0
  6. pystatsbio-1.0.0/PKG-INFO +287 -0
  7. pystatsbio-1.0.0/README.md +240 -0
  8. pystatsbio-1.0.0/docs/Forge.md +291 -0
  9. pystatsbio-1.0.0/docs/Makefile +14 -0
  10. pystatsbio-1.0.0/docs/PROGRESS.md +169 -0
  11. pystatsbio-1.0.0/docs/PYSTATSBIO_CONTEXT.md +785 -0
  12. pystatsbio-1.0.0/docs/Powerhouse.md +25 -0
  13. pystatsbio-1.0.0/docs/SGC_BIO_DIRECTIVE.md +443 -0
  14. pystatsbio-1.0.0/docs/SGC_CORE_DIRECTIVE.md +606 -0
  15. pystatsbio-1.0.0/docs/_build/html/.buildinfo +4 -0
  16. pystatsbio-1.0.0/docs/_build/html/.doctrees/__intersphinx_cache__/numpy_objects.inv +0 -0
  17. pystatsbio-1.0.0/docs/_build/html/.doctrees/__intersphinx_cache__/python_objects.inv +0 -0
  18. pystatsbio-1.0.0/docs/_build/html/.doctrees/__intersphinx_cache__/scipy_objects.inv +0 -0
  19. pystatsbio-1.0.0/docs/_build/html/.doctrees/diagnostic.doctree +0 -0
  20. pystatsbio-1.0.0/docs/_build/html/.doctrees/doseresponse.doctree +0 -0
  21. pystatsbio-1.0.0/docs/_build/html/.doctrees/environment.pickle +0 -0
  22. pystatsbio-1.0.0/docs/_build/html/.doctrees/index.doctree +0 -0
  23. pystatsbio-1.0.0/docs/_build/html/.doctrees/pk.doctree +0 -0
  24. pystatsbio-1.0.0/docs/_build/html/.doctrees/power.doctree +0 -0
  25. pystatsbio-1.0.0/docs/_build/html/_modules/index.html +300 -0
  26. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/diagnostic/_accuracy.html +486 -0
  27. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/diagnostic/_batch.html +579 -0
  28. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/diagnostic/_common.html +399 -0
  29. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/diagnostic/_cutoff.html +400 -0
  30. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/diagnostic/_roc.html +678 -0
  31. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/doseresponse/_batch.html +610 -0
  32. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/doseresponse/_bmd.html +495 -0
  33. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/doseresponse/_common.html +426 -0
  34. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/doseresponse/_fit.html +617 -0
  35. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/doseresponse/_models.html +537 -0
  36. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/doseresponse/_potency.html +464 -0
  37. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/pk/_common.html +345 -0
  38. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/pk/_nca.html +748 -0
  39. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_anova.html +565 -0
  40. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_cluster.html +428 -0
  41. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_common.html +437 -0
  42. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_crossover.html +475 -0
  43. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_means.html +528 -0
  44. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_noninferiority.html +725 -0
  45. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_proportions.html +496 -0
  46. pystatsbio-1.0.0/docs/_build/html/_modules/pystatsbio/power/_survival.html +524 -0
  47. pystatsbio-1.0.0/docs/_build/html/_sources/diagnostic.rst.txt +11 -0
  48. pystatsbio-1.0.0/docs/_build/html/_sources/doseresponse.rst.txt +11 -0
  49. pystatsbio-1.0.0/docs/_build/html/_sources/index.rst.txt +20 -0
  50. pystatsbio-1.0.0/docs/_build/html/_sources/pk.rst.txt +11 -0
  51. pystatsbio-1.0.0/docs/_build/html/_sources/power.rst.txt +11 -0
  52. pystatsbio-1.0.0/docs/_build/html/_static/base-stemmer.js +476 -0
  53. pystatsbio-1.0.0/docs/_build/html/_static/basic.css +906 -0
  54. pystatsbio-1.0.0/docs/_build/html/_static/custom.css +5 -0
  55. pystatsbio-1.0.0/docs/_build/html/_static/debug.css +69 -0
  56. pystatsbio-1.0.0/docs/_build/html/_static/doctools.js +150 -0
  57. pystatsbio-1.0.0/docs/_build/html/_static/documentation_options.js +13 -0
  58. pystatsbio-1.0.0/docs/_build/html/_static/english-stemmer.js +1066 -0
  59. pystatsbio-1.0.0/docs/_build/html/_static/file.png +0 -0
  60. pystatsbio-1.0.0/docs/_build/html/_static/language_data.js +13 -0
  61. pystatsbio-1.0.0/docs/_build/html/_static/minus.png +0 -0
  62. pystatsbio-1.0.0/docs/_build/html/_static/plus.png +0 -0
  63. pystatsbio-1.0.0/docs/_build/html/_static/pygments.css +250 -0
  64. pystatsbio-1.0.0/docs/_build/html/_static/scripts/furo-extensions.js +0 -0
  65. pystatsbio-1.0.0/docs/_build/html/_static/scripts/furo.js +3 -0
  66. pystatsbio-1.0.0/docs/_build/html/_static/scripts/furo.js.LICENSE.txt +7 -0
  67. pystatsbio-1.0.0/docs/_build/html/_static/scripts/furo.js.map +1 -0
  68. pystatsbio-1.0.0/docs/_build/html/_static/searchtools.js +693 -0
  69. pystatsbio-1.0.0/docs/_build/html/_static/skeleton.css +296 -0
  70. pystatsbio-1.0.0/docs/_build/html/_static/sphinx_highlight.js +159 -0
  71. pystatsbio-1.0.0/docs/_build/html/_static/styles/furo-extensions.css +2 -0
  72. pystatsbio-1.0.0/docs/_build/html/_static/styles/furo-extensions.css.map +1 -0
  73. pystatsbio-1.0.0/docs/_build/html/_static/styles/furo.css +2 -0
  74. pystatsbio-1.0.0/docs/_build/html/_static/styles/furo.css.map +1 -0
  75. pystatsbio-1.0.0/docs/_build/html/diagnostic.html +981 -0
  76. pystatsbio-1.0.0/docs/_build/html/doseresponse.html +1134 -0
  77. pystatsbio-1.0.0/docs/_build/html/genindex.html +849 -0
  78. pystatsbio-1.0.0/docs/_build/html/index.html +368 -0
  79. pystatsbio-1.0.0/docs/_build/html/objects.inv +0 -0
  80. pystatsbio-1.0.0/docs/_build/html/pk.html +494 -0
  81. pystatsbio-1.0.0/docs/_build/html/power.html +818 -0
  82. pystatsbio-1.0.0/docs/_build/html/py-modindex.html +321 -0
  83. pystatsbio-1.0.0/docs/_build/html/search.html +293 -0
  84. pystatsbio-1.0.0/docs/_build/html/searchindex.js +1 -0
  85. pystatsbio-1.0.0/docs/_static/custom.css +5 -0
  86. pystatsbio-1.0.0/docs/conf.py +63 -0
  87. pystatsbio-1.0.0/docs/diagnostic.rst +11 -0
  88. pystatsbio-1.0.0/docs/doseresponse.rst +11 -0
  89. pystatsbio-1.0.0/docs/index.rst +20 -0
  90. pystatsbio-1.0.0/docs/pk.rst +11 -0
  91. pystatsbio-1.0.0/docs/power.rst +11 -0
  92. pystatsbio-1.0.0/pyproject.toml +97 -0
  93. pystatsbio-1.0.0/pystatsbio/__init__.py +24 -0
  94. pystatsbio-1.0.0/pystatsbio/diagnostic/__init__.py +27 -0
  95. pystatsbio-1.0.0/pystatsbio/diagnostic/_accuracy.py +193 -0
  96. pystatsbio-1.0.0/pystatsbio/diagnostic/_batch.py +291 -0
  97. pystatsbio-1.0.0/pystatsbio/diagnostic/_common.py +110 -0
  98. pystatsbio-1.0.0/pystatsbio/diagnostic/_cutoff.py +114 -0
  99. pystatsbio-1.0.0/pystatsbio/diagnostic/_roc.py +386 -0
  100. pystatsbio-1.0.0/pystatsbio/doseresponse/__init__.py +50 -0
  101. pystatsbio-1.0.0/pystatsbio/doseresponse/_batch.py +332 -0
  102. pystatsbio-1.0.0/pystatsbio/doseresponse/_bmd.py +256 -0
  103. pystatsbio-1.0.0/pystatsbio/doseresponse/_common.py +123 -0
  104. pystatsbio-1.0.0/pystatsbio/doseresponse/_fit.py +334 -0
  105. pystatsbio-1.0.0/pystatsbio/doseresponse/_models.py +242 -0
  106. pystatsbio-1.0.0/pystatsbio/doseresponse/_potency.py +172 -0
  107. pystatsbio-1.0.0/pystatsbio/pk/__init__.py +19 -0
  108. pystatsbio-1.0.0/pystatsbio/pk/_common.py +57 -0
  109. pystatsbio-1.0.0/pystatsbio/pk/_nca.py +480 -0
  110. pystatsbio-1.0.0/pystatsbio/power/__init__.py +39 -0
  111. pystatsbio-1.0.0/pystatsbio/power/_anova.py +281 -0
  112. pystatsbio-1.0.0/pystatsbio/power/_cluster.py +145 -0
  113. pystatsbio-1.0.0/pystatsbio/power/_common.py +149 -0
  114. pystatsbio-1.0.0/pystatsbio/power/_crossover.py +192 -0
  115. pystatsbio-1.0.0/pystatsbio/power/_means.py +243 -0
  116. pystatsbio-1.0.0/pystatsbio/power/_noninferiority.py +433 -0
  117. pystatsbio-1.0.0/pystatsbio/power/_proportions.py +211 -0
  118. pystatsbio-1.0.0/pystatsbio/power/_survival.py +242 -0
  119. pystatsbio-1.0.0/pystatsbio/py.typed +0 -0
  120. pystatsbio-1.0.0/tests/__init__.py +0 -0
  121. pystatsbio-1.0.0/tests/diagnostic/__init__.py +0 -0
  122. pystatsbio-1.0.0/tests/diagnostic/test_accuracy.py +205 -0
  123. pystatsbio-1.0.0/tests/diagnostic/test_batch.py +138 -0
  124. pystatsbio-1.0.0/tests/diagnostic/test_common.py +155 -0
  125. pystatsbio-1.0.0/tests/diagnostic/test_cutoff.py +95 -0
  126. pystatsbio-1.0.0/tests/diagnostic/test_roc.py +213 -0
  127. pystatsbio-1.0.0/tests/diagnostic/test_roc_test.py +92 -0
  128. pystatsbio-1.0.0/tests/doseresponse/__init__.py +0 -0
  129. pystatsbio-1.0.0/tests/doseresponse/test_batch.py +131 -0
  130. pystatsbio-1.0.0/tests/doseresponse/test_bmd.py +133 -0
  131. pystatsbio-1.0.0/tests/doseresponse/test_common.py +165 -0
  132. pystatsbio-1.0.0/tests/doseresponse/test_fit.py +188 -0
  133. pystatsbio-1.0.0/tests/doseresponse/test_models.py +137 -0
  134. pystatsbio-1.0.0/tests/doseresponse/test_potency.py +120 -0
  135. pystatsbio-1.0.0/tests/pk/__init__.py +0 -0
  136. pystatsbio-1.0.0/tests/pk/test_common.py +146 -0
  137. pystatsbio-1.0.0/tests/pk/test_nca.py +577 -0
  138. pystatsbio-1.0.0/tests/power/__init__.py +0 -0
  139. pystatsbio-1.0.0/tests/power/test_anova.py +80 -0
  140. pystatsbio-1.0.0/tests/power/test_cluster.py +59 -0
  141. pystatsbio-1.0.0/tests/power/test_common.py +189 -0
  142. pystatsbio-1.0.0/tests/power/test_crossover.py +55 -0
  143. pystatsbio-1.0.0/tests/power/test_means.py +167 -0
  144. pystatsbio-1.0.0/tests/power/test_noninferiority.py +103 -0
  145. pystatsbio-1.0.0/tests/power/test_proportions.py +76 -0
  146. pystatsbio-1.0.0/tests/power/test_survival.py +67 -0
@@ -0,0 +1,45 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install build tools
20
+ run: pip install build
21
+
22
+ - name: Build sdist and wheel
23
+ run: python -m build
24
+
25
+ - name: Upload build artifacts
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish-pypi:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment: pypi
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - name: Download build artifacts
39
+ uses: actions/download-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ - name: Publish to PyPI
45
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ *.egg
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # IDE
16
+ .idea/
17
+ .vscode/
18
+ *.swp
19
+ *.swo
20
+
21
+ # Testing
22
+ .pytest_cache/
23
+ .coverage
24
+ htmlcov/
25
+
26
+ # OS
27
+ .DS_Store
28
+ Thumbs.db
29
+
30
+ # Fixtures (generated, but tracked)
31
+ # tests/fixtures/*.json are tracked in git
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ Initial release.
6
+
7
+ ### Modules
8
+
9
+ - **`power/`** — Sample size and power calculations for clinical trial designs:
10
+ two-sample and paired t-tests, proportions (chi-squared, Fisher exact), log-rank
11
+ (survival), one-way and factorial ANOVA, non-inferiority/equivalence/superiority
12
+ for means and proportions, crossover bioequivalence (PowerTOST method), and
13
+ cluster-randomized trials. Validated against R packages `pwr`, `TrialSize`,
14
+ `gsDesign`, `PowerTOST`, and `samplesize`.
15
+
16
+ - **`doseresponse/`** — Dose-response modeling for preclinical pharmacology:
17
+ 4-parameter log-logistic (4PL/LL.4), 5-parameter log-logistic (5PL/LL.5),
18
+ Weibull-1, Weibull-2, Brain-Cousens hormesis models. EC50/IC50 estimation,
19
+ relative potency (parallelism-tested), benchmark dose (BMD/BMDL) analysis,
20
+ and GPU-accelerated batch fitting for high-throughput screening. Validated
21
+ against R packages `drc` and `BMDS`.
22
+
23
+ - **`diagnostic/`** — Diagnostic accuracy analysis for biomarker evaluation:
24
+ ROC curves with DeLong AUC and confidence intervals, Delong AUC comparison test,
25
+ sensitivity/specificity/PPV/NPV/likelihood ratios at any threshold, optimal
26
+ cutoff selection (Youden, min-distance, cost-weighted), and batch AUC computation
27
+ for biomarker panel screening. Validated against R packages `pROC`,
28
+ `OptimalCutpoints`, and `epiR`.
29
+
30
+ - **`pk/`** — Non-compartmental pharmacokinetic analysis (NCA): AUC (linear,
31
+ log-linear, linear-up/log-down trapezoidal), Cmax, Tmax, terminal elimination
32
+ rate constant (lambda_z), half-life, clearance (IV and extravascular), volume of
33
+ distribution, AUMC, and MRT. Validated against R packages `PKNCA` and
34
+ `NonCompart`.
@@ -0,0 +1,190 @@
1
+ # Coding Bible
2
+
3
+ ## Preamble
4
+
5
+ This document is not a style guide. It is not a set of preferences. It is not a list of
6
+ suggestions that can be weighed against convenience or time pressure.
7
+
8
+ Every rule in this document exists because its absence has caused real, costly, documented
9
+ failures in real systems — bugs that took days to trace, security holes that went undetected
10
+ for months, codebases that became unmaintainable within a year of being written. The rules
11
+ are the scar tissue. They are what you get when you ask experienced engineers what they wish
12
+ someone had forced them to do the first time.
13
+
14
+ When you are implementing something and a rule feels inconvenient, that feeling is a signal,
15
+ not a reason to deviate. Inconvenience usually means the rule is doing its job — preventing
16
+ the path of least resistance from becoming a future liability.
17
+
18
+ **These rules are non-negotiable and not subject to contextual override.** The following
19
+ exceptions are not acceptable justifications for deviation:
20
+
21
+ - "This is a prototype / MVP / quick fix." Prototypes become production. Quick fixes become
22
+ permanent. Code written without discipline under time pressure is the most dangerous code
23
+ that exists.
24
+ - "This is a small module that doesn't need it." Small modules grow. The time to enforce
25
+ structure is before the module is large enough to make restructuring painful.
26
+ - "The architecture requires it." If the architecture requires violating these rules, the
27
+ architecture is wrong. Fix the architecture.
28
+ - "It would take too long to do it correctly." It will take longer to fix it later. It always
29
+ does.
30
+
31
+ If a genuine, documented edge case exists where a rule cannot be followed, that exception
32
+ must be explicitly marked in a comment at the relevant location, stating which rule is being
33
+ waived, why, and what mitigating measures are in place. Undocumented deviations are bugs.
34
+
35
+ ---
36
+
37
+ ## The Underlying Principle
38
+
39
+ All seven rules are expressions of one idea:
40
+
41
+ **Make the wrong thing hard to do accidentally.**
42
+
43
+ Good architecture is not about making correct behavior possible. Correct behavior is always
44
+ possible. Good architecture is about making incorrect behavior require deliberate, visible,
45
+ documented effort — so that mistakes are loud, localized, and recoverable rather than silent,
46
+ diffuse, and permanent.
47
+
48
+ When in doubt, ask: *am I making the wrong thing easy to do accidentally?* If yes, stop and
49
+ apply the relevant rule before continuing.
50
+
51
+ ---
52
+
53
+ These rules are non-negotiable. They exist because every one of them corresponds to a
54
+ real failure mode with real consequences. If you think a rule doesn't apply to a specific
55
+ case, you are probably wrong. If you are certain it doesn't apply, document why explicitly
56
+ in a comment before proceeding.
57
+
58
+ ---
59
+
60
+ ## 1. Fail Fast, Fail Loud, No Defaults
61
+
62
+ Do not insert default behavior that fails silently. If something breaks, the system must
63
+ break immediately, obviously, and traceably.
64
+
65
+ **Rules:**
66
+ - Raise explicit, descriptive errors. Never swallow exceptions.
67
+ - Never return a default value to mask a missing or invalid state.
68
+ - Log the failure with enough context to reproduce it.
69
+
70
+ **Corollary — No Optimistic Assumptions:**
71
+ Do not assume inputs are valid, dependencies are available, or external calls succeed.
72
+ Assert and verify. If an assumption is required, document it and enforce it with a guard.
73
+
74
+ ---
75
+
76
+ ## 2. Trust Your Neighbors — Validate Input, Not Output
77
+
78
+ You own your output contract. You do not trust anyone else's input contract.
79
+
80
+ **Rules:**
81
+ - All external inputs (API calls, user input, file reads, environment variables) must be
82
+ validated at the boundary before use.
83
+ - Internal module output does not need re-validation by the caller — the module is
84
+ responsible for the correctness of what it emits.
85
+
86
+ **Corollary — Define Your Contracts:**
87
+ Every module must have an explicit, documented input/output contract. If a caller violates
88
+ the input contract, the module must fail loudly (see Rule 1), not silently compensate.
89
+
90
+ ---
91
+
92
+ ## 3. UNIX Philosophy — One Module, One Job
93
+
94
+ Each module does one thing only, and does that one thing well. If a module needs to do
95
+ multiple things, it must be split into multiple files.
96
+
97
+ **Rules:**
98
+ - No file should be the sole owner of more than one domain concept.
99
+ - If you find yourself writing "and" when describing what a file does, split it.
100
+ - Tight coupling between modules is a design failure, not an implementation detail.
101
+
102
+ **Corollary — No God Files:**
103
+ A module that knows everything, owns everything, or touches everything is a liability.
104
+ If removing a module would require changes across more than two other modules, it is
105
+ probably doing too much.
106
+
107
+ ---
108
+
109
+ ## 4. LOC Limits — No Bloated Monoliths
110
+
111
+ Files must not become monoliths. Complexity must be managed through decomposition,
112
+ not consolidation.
113
+
114
+ **Rules:**
115
+ - **Hard limit: 500 lines of code** (comments and docstrings excluded). Do not exceed this
116
+ under any circumstances.
117
+ - **Soft limit: 400 lines of code.** Above 400 lines, actively look for split opportunities.
118
+ - If a file "requires" more than 500 lines, that is a signal the abstraction is wrong —
119
+ split it into multiple focused files.
120
+
121
+ **Corollary — Splitting Is Not Optional:**
122
+ Exceeding the hard limit is never acceptable, even temporarily. Split first, implement after.
123
+
124
+ ---
125
+
126
+ ## 5. No Hidden State — Explicit Data Flow
127
+
128
+ All state must be explicitly passed or managed through clearly defined interfaces.
129
+ Hidden state is a bug waiting to be discovered at the worst possible time.
130
+
131
+ **Rules:**
132
+ - No global variables. No module-level mutable state unless explicitly documented and
133
+ architecturally justified.
134
+ - No implicit state — if a function's behavior depends on something, that something must
135
+ appear in its signature or be explicitly injected.
136
+ - No hidden coupling between modules. If two modules share state, that relationship must
137
+ be visible and intentional.
138
+
139
+ **Corollary — Spooky Action at a Distance Is a Bug:**
140
+ If changing module A causes unexpected behavior in module B, and there is no explicit
141
+ interface between them, the architecture is broken. Fix the architecture, not the symptom.
142
+
143
+ ---
144
+
145
+ ## 6. Deterministic Behavior by Default
146
+
147
+ All functions must be deterministic unless explicitly documented otherwise.
148
+
149
+ **Rules:**
150
+ - No randomness, time-dependent logic, or non-deterministic behavior without explicit
151
+ documentation and an injectable seed or clock interface for testing.
152
+ - Functions that depend on system time, random state, or external non-deterministic
153
+ sources must be flagged with a `# NON-DETERMINISTIC:` comment explaining why.
154
+ - Non-deterministic behavior must be isolatable — wrap it so the rest of the system
155
+ can be tested deterministically.
156
+
157
+ **Corollary — Reproducibility Is Not Optional:**
158
+ If a bug cannot be reliably reproduced, it cannot be reliably fixed. Non-determinism
159
+ is the enemy of reproducibility. Treat it like a dependency that must be explicitly
160
+ managed, not a feature.
161
+
162
+ ---
163
+
164
+ ## 7. Tests Are First-Class Citizens
165
+
166
+ Every module must have corresponding tests. Tests are not optional, not afterthoughts,
167
+ and not someone else's job.
168
+
169
+ **Rules:**
170
+ - Every module must have a corresponding test file covering:
171
+ - **Normal cases** — expected inputs produce expected outputs.
172
+ - **Edge cases** — boundary conditions, empty inputs, maximum values, type boundaries.
173
+ - **Failure cases** — invalid inputs produce the correct explicit errors (see Rule 1).
174
+ - No module ships without tests. No exceptions.
175
+ - Tests must be runnable in isolation — no test should depend on the state left by
176
+ another test.
177
+
178
+ **Corollary — Untested Code Is Unverified Assumptions:**
179
+ If there is no test for a behavior, that behavior is assumed, not verified. Assumptions
180
+ accumulate into system failures. If you cannot write a test for something, that is a signal
181
+ the design is wrong.
182
+
183
+ ---
184
+
185
+ ## Meta-Rule
186
+
187
+ If any of these rules feel inconvenient, that feeling is the point. These rules exist
188
+ precisely because the convenient path leads to the failure modes each rule was written
189
+ to prevent. When in doubt, ask: *am I making the wrong thing easy to do accidentally?*
190
+ If yes, apply the relevant rule.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hai-Shuo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystatsbio
3
+ Version: 1.0.0
4
+ Summary: Biotech and pharmaceutical statistical computing for Python
5
+ Project-URL: Homepage, https://sgcx.org/technology/pystatsbio/
6
+ Project-URL: Documentation, https://sgcx.org/docs/pystatsbio/
7
+ Project-URL: Repository, https://github.com/sgcx-org/pystatsbio
8
+ Project-URL: Issues, https://github.com/sgcx-org/pystatsbio/issues
9
+ Author-email: Hai-Shuo <contact@sgcx.org>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: biostatistics,clinical-trials,diagnostics,dose-response,gpu,pharmacokinetics,power-analysis,sample-size
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: numpy>=1.24
26
+ Requires-Dist: pystatistics>=0.1.0
27
+ Requires-Dist: scipy>=1.10
28
+ Provides-Extra: all
29
+ Requires-Dist: furo; extra == 'all'
30
+ Requires-Dist: mypy>=1.0; extra == 'all'
31
+ Requires-Dist: pytest-cov>=4.0; extra == 'all'
32
+ Requires-Dist: pytest>=7.0; extra == 'all'
33
+ Requires-Dist: ruff>=0.1; extra == 'all'
34
+ Requires-Dist: sphinx>=6.0; extra == 'all'
35
+ Requires-Dist: torch>=2.0; extra == 'all'
36
+ Provides-Extra: dev
37
+ Requires-Dist: mypy>=1.0; extra == 'dev'
38
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
39
+ Requires-Dist: pytest>=7.0; extra == 'dev'
40
+ Requires-Dist: ruff>=0.1; extra == 'dev'
41
+ Provides-Extra: docs
42
+ Requires-Dist: furo; extra == 'docs'
43
+ Requires-Dist: sphinx>=6.0; extra == 'docs'
44
+ Provides-Extra: gpu
45
+ Requires-Dist: torch>=2.0; extra == 'gpu'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # PyStatsBio
49
+
50
+ Biotech and pharmaceutical statistical computing for Python.
51
+
52
+ Built on [PyStatistics](https://github.com/sgcx-org/pystatistics) for the general statistical
53
+ computing layer. PyStatsBio provides domain-specific methods for the drug development pipeline:
54
+ dose-response modeling, sample size/power, diagnostic accuracy, and non-compartmental
55
+ pharmacokinetics.
56
+
57
+ ---
58
+
59
+ ## Design Philosophy
60
+
61
+ PyStatsBio follows the same principles as PyStatistics:
62
+
63
+ 1. **Fail fast, fail loud** — no silent fallbacks or "helpful" defaults
64
+ 2. **Explicit over implicit** — require parameters, don't assume intent
65
+ 3. **R-level validation** — every function is validated against a named R reference
66
+
67
+ Each function states exactly which R function it replicates and to what tolerance.
68
+
69
+ ---
70
+
71
+ ## Quick Start
72
+
73
+ ```python
74
+ # --- Clinical trial power / sample size ---
75
+ from pystatsbio import power
76
+
77
+ # Solve for sample size (two-sample t-test)
78
+ result = power.power_t_test(d=0.5, power=0.80, alpha=0.05, type="two.sample")
79
+ print(result.n) # per-group sample size
80
+ print(result.summary())
81
+
82
+ # Solve for power given n
83
+ result = power.power_t_test(n=64, d=0.5, alpha=0.05, type="two.sample")
84
+ print(result.power)
85
+
86
+ # Paired t-test
87
+ result = power.power_paired_t_test(d=0.3, power=0.80, alpha=0.05)
88
+ print(result.n)
89
+
90
+ # Proportions
91
+ result = power.power_prop_test(p1=0.30, p2=0.50, power=0.80, alpha=0.05)
92
+ print(result.n)
93
+
94
+ # Survival (log-rank)
95
+ result = power.power_logrank(
96
+ p1=0.60, p2=0.40, accrual=12, follow=24, power=0.80, alpha=0.05
97
+ )
98
+ print(result.n_events, result.n_total)
99
+
100
+ # Non-inferiority for means
101
+ result = power.power_noninf_mean(
102
+ delta=0.0, sigma=1.0, margin=0.5, power=0.80, alpha=0.05
103
+ )
104
+ print(result.n)
105
+
106
+ # Crossover bioequivalence (PowerTOST method)
107
+ result = power.power_crossover_be(cv=0.20, power=0.80, alpha=0.05)
108
+ print(result.n) # subjects per sequence
109
+
110
+ # Cluster-randomized trial
111
+ result = power.power_cluster(
112
+ d=0.5, icc=0.05, m=20, power=0.80, alpha=0.05
113
+ )
114
+ print(result.n_clusters)
115
+
116
+
117
+ # --- Dose-response modeling ---
118
+ import numpy as np
119
+ from pystatsbio import doseresponse
120
+
121
+ dose = np.array([0.001, 0.01, 0.1, 1.0, 10.0, 100.0])
122
+ response = np.array([2.1, 3.5, 12.0, 48.0, 87.5, 97.8])
123
+
124
+ # Fit 4PL (LL.4) model
125
+ result = doseresponse.fit_drm(dose, response, model="LL.4")
126
+ print(result.params) # CurveParams(b, c, d, e) — Hill slope, lower, upper, EC50
127
+ print(result.ec50)
128
+ print(result.summary())
129
+
130
+ # Fit 5PL (asymmetric)
131
+ result = doseresponse.fit_drm(dose, response, model="LL.5")
132
+ print(result.params)
133
+
134
+ # Extract EC50 with confidence interval
135
+ ec50_result = doseresponse.ec50(result)
136
+ print(ec50_result.ec50, ec50_result.ci_lower, ec50_result.ci_upper)
137
+
138
+ # Relative potency (reference vs. test compound)
139
+ ref_result = doseresponse.fit_drm(dose, response, model="LL.4")
140
+ test_result = doseresponse.fit_drm(dose * 3, response, model="LL.4")
141
+ rp = doseresponse.relative_potency(ref_result, test_result)
142
+ print(rp.ratio, rp.ci_lower, rp.ci_upper, rp.parallel)
143
+
144
+ # Benchmark dose (BMD/BMDL)
145
+ bmd_result = doseresponse.bmd(result, bmr=0.10)
146
+ print(bmd_result.bmd, bmd_result.bmdl)
147
+
148
+ # Batch fitting (GPU-accelerated for HTS)
149
+ # responses: (n_curves, n_doses) array
150
+ responses = np.random.rand(500, 6) * 100
151
+ batch = doseresponse.fit_drm_batch(dose, responses, model="LL.4", backend="auto")
152
+ print(batch.ec50) # shape (500,)
153
+ print(batch.params) # CurveParams with shape-(500,) arrays
154
+
155
+
156
+ # --- Diagnostic accuracy ---
157
+ from pystatsbio import diagnostic
158
+
159
+ scores = np.array([0.1, 0.4, 0.35, 0.8, 0.9, 0.15, 0.6, 0.75, 0.55, 0.95])
160
+ labels = np.array([0, 0, 0, 1, 1, 0, 1, 1, 0, 1])
161
+
162
+ # ROC curve + AUC
163
+ roc_result = diagnostic.roc(scores, labels)
164
+ print(roc_result.auc, roc_result.ci_lower, roc_result.ci_upper)
165
+ print(roc_result.sensitivity, roc_result.specificity)
166
+
167
+ # Compare two biomarkers (DeLong test)
168
+ scores2 = np.random.rand(10)
169
+ test_result = diagnostic.roc_test(scores, scores2, labels)
170
+ print(test_result.p_value, test_result.summary())
171
+
172
+ # Full diagnostic accuracy at a threshold
173
+ da = diagnostic.diagnostic_accuracy(scores, labels, threshold=0.5)
174
+ print(da.sensitivity, da.specificity, da.ppv, da.npv, da.lr_pos, da.lr_neg)
175
+
176
+ # Optimal cutoff selection
177
+ cutoff = diagnostic.optimal_cutoff(roc_result, method="youden")
178
+ print(cutoff.threshold, cutoff.sensitivity, cutoff.specificity, cutoff.youden_index)
179
+
180
+ # Batch AUC for biomarker panel screening
181
+ # panel: (n_biomarkers, n_subjects) array
182
+ panel = np.random.rand(200, 100)
183
+ batch_auc = diagnostic.batch_auc(panel, labels[:100] if len(labels) >= 100 else np.random.randint(0, 2, 100))
184
+ print(batch_auc.auc) # shape (200,) — one AUC per biomarker
185
+
186
+
187
+ # --- Pharmacokinetics (NCA) ---
188
+ from pystatsbio import pk
189
+
190
+ time = np.array([0, 0.5, 1, 2, 4, 6, 8, 12, 24])
191
+ conc = np.array([0, 8.5, 12.1, 10.4, 7.2, 4.9, 3.1, 1.4, 0.3])
192
+
193
+ result = pk.nca(time, conc, route="ev", dose=100.0)
194
+ print(result.cmax, result.tmax)
195
+ print(result.auc_last, result.auc_inf)
196
+ print(result.half_life)
197
+ print(result.cl, result.vd)
198
+ print(result.aumc_last, result.mrt)
199
+ print(result.summary())
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Modules
205
+
206
+ | Module | Status | Description |
207
+ |--------|--------|-------------|
208
+ | `power/` | Complete | Sample size and power for clinical trial designs |
209
+ | `doseresponse/` | Complete | 4PL/5PL curve fitting, EC50, relative potency, BMD, batch HTS |
210
+ | `diagnostic/` | Complete | ROC, AUC, sensitivity/specificity, optimal cutoff, batch biomarker |
211
+ | `pk/` | Complete | Non-compartmental PK analysis (NCA): AUC, Cmax, CL, Vd, MRT |
212
+
213
+ ### `power` — Sample Size and Power
214
+
215
+ | Function | R equivalent |
216
+ |----------|--------------|
217
+ | `power_t_test()` | `pwr::pwr.t.test()` |
218
+ | `power_paired_t_test()` | `pwr::pwr.t.test(type="paired")` |
219
+ | `power_prop_test()` | `pwr::pwr.2p.test()` |
220
+ | `power_fisher_test()` | `TrialSize::TwoSampleProportion.Equality()` |
221
+ | `power_logrank()` | `gsDesign::nSurv()` |
222
+ | `power_anova_oneway()` | `pwr::pwr.anova.test()` |
223
+ | `power_anova_factorial()` | `TrialSize::FactorialDesign()` |
224
+ | `power_noninf_mean()` | `TrialSize::TwoSampleMean.NIS()` |
225
+ | `power_noninf_prop()` | `TrialSize::TwoSampleProportion.NIS()` |
226
+ | `power_equiv_mean()` | `TrialSize::TwoSampleMean.Equivalence()` |
227
+ | `power_superiority_mean()` | `TrialSize::TwoSampleMean.Superiority()` |
228
+ | `power_crossover_be()` | `PowerTOST::sampleSize()` |
229
+ | `power_cluster()` | `samplesize::n.twogroup()` with ICC |
230
+
231
+ ### `doseresponse` — Dose-Response Modeling
232
+
233
+ | Function | R equivalent |
234
+ |----------|--------------|
235
+ | `fit_drm()` | `drc::drm()` |
236
+ | `fit_drm_batch()` | vectorized `drc::drm()` |
237
+ | `ec50()` | `drc::ED()` |
238
+ | `relative_potency()` | `drc::EDcomp()` with parallelism test |
239
+ | `bmd()` | `drc::bmd()` / `BMDS` |
240
+
241
+ Models: `LL.4` (4PL), `LL.5` (5PL), `W1.4` (Weibull-1), `W2.4` (Weibull-2),
242
+ `BC.4` (Brain-Cousens hormesis).
243
+
244
+ ### `diagnostic` — Diagnostic Accuracy
245
+
246
+ | Function | R equivalent |
247
+ |----------|-------------|
248
+ | `roc()` | `pROC::roc()` with DeLong CI |
249
+ | `roc_test()` | `pROC::roc.test()` (DeLong) |
250
+ | `diagnostic_accuracy()` | `epiR::epi.tests()` |
251
+ | `optimal_cutoff()` | `OptimalCutpoints::optimal.cutpoints()` |
252
+ | `batch_auc()` | vectorized `pROC::auc()` |
253
+
254
+ ### `pk` — Non-Compartmental Analysis
255
+
256
+ | Function | R equivalent |
257
+ |----------|-------------|
258
+ | `nca()` | `PKNCA::pk.nca()` / `NonCompart::sNCA()` |
259
+
260
+ AUC methods: `linear`, `log`, `linear-up/log-down` (FDA default).
261
+ Routes: `iv` (intravenous), `ev` (extravascular).
262
+
263
+ ---
264
+
265
+ ## Installation
266
+
267
+ ```bash
268
+ pip install pystatsbio
269
+
270
+ # With GPU support (requires PyTorch)
271
+ pip install pystatsbio[gpu]
272
+
273
+ # Development
274
+ pip install pystatsbio[dev]
275
+ ```
276
+
277
+ Requires Python 3.11+. Core dependencies: `pystatistics`, `numpy`, `scipy`.
278
+
279
+ ---
280
+
281
+ ## License
282
+
283
+ MIT
284
+
285
+ ## Author
286
+
287
+ Hai-Shuo (contact@sgcx.org)