pyplasmode 1.0.0__tar.gz → 1.2.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.
- pyplasmode-1.2.0/CHANGELOG.md +60 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/CITATION.cff +1 -1
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/CONTRIBUTING.md +11 -1
- pyplasmode-1.2.0/PKG-INFO +168 -0
- pyplasmode-1.2.0/README.md +138 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/api.md +12 -5
- pyplasmode-1.2.0/docs/assets/concepts/README.md +8 -0
- pyplasmode-1.2.0/docs/benchmark-demo.md +80 -0
- pyplasmode-1.2.0/docs/calibration.md +108 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/evaluation.md +86 -28
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/gallery.md +6 -6
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/index.md +27 -11
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/methods.md +38 -21
- pyplasmode-1.2.0/docs/metrics.md +174 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/outcomes.md +18 -14
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/privacy-and-limitations.md +6 -2
- pyplasmode-1.2.0/docs/references.md +77 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/structures-and-extensions.md +5 -4
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/truths.md +6 -4
- pyplasmode-1.2.0/docs/tutorial.md +179 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/validation.md +23 -9
- pyplasmode-1.2.0/examples/benchmark_recovery_demo.py +163 -0
- pyplasmode-1.2.0/examples/metrics_calibration.py +77 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/examples/quickstart.py +3 -3
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/examples/survival.py +3 -2
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/mkdocs.yml +6 -2
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/pyproject.toml +13 -7
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/__init__.py +8 -2
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/data.py +3 -3
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/evaluate.py +99 -10
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/generate.py +3 -3
- pyplasmode-1.2.0/src/pyplasmode/metrics.py +415 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/nominations.py +74 -58
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/outcomes.py +180 -19
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/results.py +22 -2
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/structures.py +5 -5
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/truth.py +2 -2
- pyplasmode-1.2.0/tests/integration/test_benchmark_demo.py +56 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/integration/test_documented_workflows.py +23 -3
- pyplasmode-1.2.0/tests/statistical/test_auc_calibration.py +62 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/statistical/test_matched_recovery.py +65 -0
- pyplasmode-1.2.0/tests/statistical/test_metrics.py +100 -0
- pyplasmode-1.2.0/tests/unit/test_auc_calibration.py +176 -0
- pyplasmode-1.2.0/tests/unit/test_metrics.py +268 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_nominations.py +20 -1
- pyplasmode-1.2.0/tests/unit/test_paired_replicates.py +115 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_public_contracts.py +7 -7
- pyplasmode-1.0.0/CHANGELOG.md +0 -18
- pyplasmode-1.0.0/PKG-INFO +0 -142
- pyplasmode-1.0.0/README.md +0 -116
- pyplasmode-1.0.0/docs/assets/concepts/README.md +0 -10
- pyplasmode-1.0.0/docs/assets/concepts/plasmode-workflow.pdf +0 -0
- pyplasmode-1.0.0/docs/assets/concepts/signal-recovery.pdf +0 -0
- pyplasmode-1.0.0/docs/references.md +0 -30
- pyplasmode-1.0.0/docs/tutorial.md +0 -140
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/.gitignore +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/LICENSE +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/THIRD_PARTY_NOTICES.md +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/concepts/plasmode-workflow.png +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/concepts/signal-recovery.png +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/correlated-recovery.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/correlated-recovery.pdf +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/correlated-recovery.png +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/correlated-recovery.svg +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/feature-correlations.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/outcome-calibration.json +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/outcome-families.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/outcome-families.pdf +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/outcome-families.png +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/outcome-families.svg +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/signal-shapes.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/signal-shapes.pdf +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/signal-shapes.png +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/patterns/signal-shapes.svg +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/external-conformance.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/null-control.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/outcome-reproducibility.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/truth-recovery.csv +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/validation.pdf +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/validation.png +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/docs/assets/validation/validation.svg +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/examples/custom_signal.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/examples/pattern_gallery.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/examples/recovery.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/examples/validation_vignette.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/_validation.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/src/pyplasmode/py.typed +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/__init__.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/integration/__init__.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/integration/test_fractional_recovery.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/integration/test_generate_evaluate.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/integration/test_outcome_composition.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/integration/test_pattern_gallery.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/statistical/__init__.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/statistical/test_evaluate.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/statistical/test_outcomes.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/statistical/test_resampling.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/__init__.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_data.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_evaluate.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_outcomes.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_structures.py +0 -0
- {pyplasmode-1.0.0 → pyplasmode-1.2.0}/tests/unit/test_truth.py +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0 — 2026-11-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `pyplasmode.metrics`: chance-corrected selection stability with unequal budgets
|
|
8
|
+
and ties, tie-aware RBO, inclusion-first consensus ranks, IPCW cumulative/dynamic
|
|
9
|
+
AUROC, paired participant bootstrap differences and oracle-minus-fitted
|
|
10
|
+
`discrimination_gap` (also available as `learnability_gap`).
|
|
11
|
+
- `target_auc` for binary and survival outcomes, reporting expected oracle AUROC
|
|
12
|
+
and the solved odds/hazard ratio while retaining marginal risk.
|
|
13
|
+
- Metrics and calibration guides, executable examples and a synthetic Cox,
|
|
14
|
+
ridge and XGBoost benchmark comparing held-out AUROC and chance-adjusted recovery.
|
|
15
|
+
Model dependencies are supplied by the optional `benchmark` extra.
|
|
16
|
+
- Identity-aligned paired replicate comparisons with explicit failed/unmatched
|
|
17
|
+
pair counts and conditional Monte Carlo uncertainty.
|
|
18
|
+
- `allow_subset=True` for fractional recovery, matched recovery and outcome
|
|
19
|
+
specificity, preserving generating denominators and the available-panel reference.
|
|
20
|
+
- Python 3.15 support and NumPy string arrays as bootstrap participant IDs.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- RBO defaults to Webber whole-tie prefixes; independent tie resolution remains
|
|
25
|
+
available through `tie_method="independent"`.
|
|
26
|
+
- Censored AUROC defaults to exact score ties. Use `tied_tolerance=1e-8` to
|
|
27
|
+
reproduce scikit-survival's default score-tie convention.
|
|
28
|
+
- Default survival AUROC horizons precede the administrative cutoff; an explicit
|
|
29
|
+
later administrative cutoff is supported.
|
|
30
|
+
- Calibration parameters support direct lookup, and `metrics` is discoverable
|
|
31
|
+
through the root package. Unattainable target AUROCs raise explicitly.
|
|
32
|
+
- The validation extra requires statsmodels 0.14.6 or later. Source distributions
|
|
33
|
+
omit the unreferenced concept PDFs, which remain in the repository.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Never-selected consensus features sort below selected features even with wide ties.
|
|
38
|
+
- AUROC-targeted calibration tolerates single extreme signal observations and
|
|
39
|
+
rejects requests above the supported effect bracket without clipping.
|
|
40
|
+
- Edgeless graph regions report unavailable edge coverage and expose the target
|
|
41
|
+
edge count while retaining node recovery.
|
|
42
|
+
- Panel restrictions retain module weights, group proxies and joint interaction
|
|
43
|
+
requirements rather than redefining the generating truth.
|
|
44
|
+
|
|
45
|
+
## 1.0.0
|
|
46
|
+
|
|
47
|
+
First public release. PyPlasmode provides outcome generation, feature-recovery evaluation,
|
|
48
|
+
worked examples and reference validation for biomarker benchmarking.
|
|
49
|
+
|
|
50
|
+
### Included in this release
|
|
51
|
+
|
|
52
|
+
- Empirical complete-row resampling and source-partitioned generation.
|
|
53
|
+
- Null, sparse, correlated-group, module, interaction and custom signals.
|
|
54
|
+
- Five outcome families calibrated in observable units.
|
|
55
|
+
- Recovery, prediction, panel, reconstruction and rank-stability evaluation.
|
|
56
|
+
- API documentation, worked examples, validation figures and cross-platform CI.
|
|
57
|
+
- A visual gallery of signal shapes, correlated-feature recovery and outcome families,
|
|
58
|
+
with editable vector figures and an executable source-data example.
|
|
59
|
+
- Python 3.12-3.14 and scikit-learn 1.6 through the current 1.x series.
|
|
60
|
+
- BSD 3-Clause licensing.
|
|
@@ -2,7 +2,7 @@ cff-version: 1.2.0
|
|
|
2
2
|
message: "If you use PyPlasmode in your research, please cite this software using the metadata below."
|
|
3
3
|
title: "PyPlasmode"
|
|
4
4
|
type: software
|
|
5
|
-
version: 1.
|
|
5
|
+
version: 1.2.0
|
|
6
6
|
repository-code: "https://github.com/BradSegal/PyPlasmode"
|
|
7
7
|
license: BSD-3-Clause
|
|
8
8
|
authors:
|
|
@@ -4,7 +4,7 @@ Contributions to generators, evaluation functions, documentation and examples ar
|
|
|
4
4
|
For a new signal or outcome family, describe the intended use and mathematical definition
|
|
5
5
|
in an issue before implementing it.
|
|
6
6
|
|
|
7
|
-
From a checkout, install a supported Python version (3.12-3.
|
|
7
|
+
From a checkout, install a supported Python version (3.12-3.15) and uv, then run:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
uv sync --extra validation --group docs
|
|
@@ -18,6 +18,10 @@ uv run twine check --strict dist/*
|
|
|
18
18
|
uv run check-wheel-contents dist/*.whl
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
Add `--extra benchmark` to `uv sync` to run the synthetic Cox/ridge/XGBoost
|
|
22
|
+
example and its integration test. Without those optional dependencies the
|
|
23
|
+
integration test is explicitly skipped.
|
|
24
|
+
|
|
21
25
|
Run `python examples/validation_vignette.py --output validation-output` to regenerate the
|
|
22
26
|
synthetic validation figures and tables. The `validation` extra is included in the setup above.
|
|
23
27
|
|
|
@@ -46,3 +50,9 @@ Record random seeds and dependency versions when sharing reproducible analyses.
|
|
|
46
50
|
The CI matrix covers minimum numerical dependencies on Python 3.12 and current dependencies
|
|
47
51
|
on supported Python versions, with Linux, Windows and macOS jobs. Test dependency changes
|
|
48
52
|
against this matrix. Build release artifacts from the tagged version after CI passes.
|
|
53
|
+
|
|
54
|
+
Attach the checked wheel and source distribution to a draft GitHub release, then publish
|
|
55
|
+
the release. The `publish.yml` workflow validates those distributions and uploads them to
|
|
56
|
+
PyPI through the `pypi` trusted-publishing environment. For an existing release, run
|
|
57
|
+
`gh workflow run publish.yml --ref main -f tag=v1.0.0` with its tag. No PyPI API token is
|
|
58
|
+
required, and already-published versions must not be replaced.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyplasmode
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Simulate outcomes from feature data and evaluate prediction and feature selection.
|
|
5
|
+
Project-URL: Repository, https://github.com/BradSegal/PyPlasmode
|
|
6
|
+
Project-URL: Documentation, https://bradsegal.github.io/PyPlasmode/
|
|
7
|
+
Project-URL: Issues, https://github.com/BradSegal/PyPlasmode/issues
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
18
|
+
Requires-Python: <3.16,>=3.12
|
|
19
|
+
Requires-Dist: numpy<3,>=2.0
|
|
20
|
+
Requires-Dist: scikit-learn<2,>=1.6
|
|
21
|
+
Requires-Dist: scipy<2,>=1.14
|
|
22
|
+
Provides-Extra: benchmark
|
|
23
|
+
Requires-Dist: scikit-survival<1,>=0.24; extra == 'benchmark'
|
|
24
|
+
Requires-Dist: threadpoolctl<4,>=3.1; extra == 'benchmark'
|
|
25
|
+
Requires-Dist: xgboost<4,>=2.1; extra == 'benchmark'
|
|
26
|
+
Provides-Extra: validation
|
|
27
|
+
Requires-Dist: matplotlib<4,>=3.10; extra == 'validation'
|
|
28
|
+
Requires-Dist: statsmodels<0.16,>=0.14.6; extra == 'validation'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# PyPlasmode
|
|
32
|
+
|
|
33
|
+
PyPlasmode tests prediction and feature selection against a known signal. It combines your
|
|
34
|
+
feature data with simulated outcomes, so you can measure both how accurately a model predicts
|
|
35
|
+
and how well it identifies the features used to generate those outcomes.
|
|
36
|
+
|
|
37
|
+
Built for biomarker benchmarking, the library works with numeric tabular data more generally.
|
|
38
|
+
Its inputs are a matrix of observations and named features, not a particular assay or dataset.
|
|
39
|
+
A plasmode uses observed measurements to retain realistic feature relationships while you
|
|
40
|
+
control the outcome-generating mechanism. Synthetic matrices work too, as in the examples below.
|
|
41
|
+
|
|
42
|
+
Use it with your existing modelling workflow: generate a dataset, fit and tune your models,
|
|
43
|
+
then evaluate their predictions, feature rankings or selected panels.
|
|
44
|
+
|
|
45
|
+
[](https://bradsegal.github.io/PyPlasmode/methods/)
|
|
46
|
+
|
|
47
|
+
The same generated dataset supports two comparisons: predictions against outcomes, and
|
|
48
|
+
selected features against the signal that generated them.
|
|
49
|
+
|
|
50
|
+
Explore the [visual gallery](https://bradsegal.github.io/PyPlasmode/gallery/) for signal patterns, correlated-feature recovery
|
|
51
|
+
and the five outcome families.
|
|
52
|
+
|
|
53
|
+
The [feature-selection tutorial](https://bradsegal.github.io/PyPlasmode/tutorial/) follows a complete example from correlated
|
|
54
|
+
measurements through model fitting, attribution and recovery above chance.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
Requires Python 3.12-3.15. Install PyPlasmode 1.2.0 from [PyPI](https://pypi.org/project/pyplasmode/):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python -m pip install pyplasmode
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The runtime dependencies are NumPy, SciPy and scikit-learn. To include the
|
|
65
|
+
reference validation and survival examples:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python -m pip install "pyplasmode[validation]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Ten-minute example
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import numpy as np
|
|
75
|
+
from sklearn.linear_model import LogisticRegression
|
|
76
|
+
import pyplasmode as ppm
|
|
77
|
+
|
|
78
|
+
rng = np.random.default_rng(1)
|
|
79
|
+
population = ppm.Population(
|
|
80
|
+
rng.normal(size=(2_000, 100)),
|
|
81
|
+
tuple(f"feature_{index}" for index in range(100)),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
partition = ppm.partition_population(
|
|
85
|
+
population,
|
|
86
|
+
training_fraction=0.6,
|
|
87
|
+
validation_fraction=0.2,
|
|
88
|
+
seed=7,
|
|
89
|
+
)
|
|
90
|
+
samples = ppm.generate_partitioned(
|
|
91
|
+
partition,
|
|
92
|
+
truth=ppm.SparseTruth(features=("feature_3", "feature_17", "feature_42")),
|
|
93
|
+
outcome=ppm.BinaryOutcome(probability=0.15, odds_ratio=2.0),
|
|
94
|
+
sample_sizes=ppm.PartitionSampleSizes(1_200, 400, 400),
|
|
95
|
+
sampling_method="without_replacement",
|
|
96
|
+
seed=42,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
train, test = samples.training, samples.evaluation
|
|
100
|
+
model = LogisticRegression(max_iter=1_000).fit(train.X, train.outcome.values)
|
|
101
|
+
ranking = tuple(train.feature_ids[index] for index in np.argsort(-np.abs(model.coef_[0])))
|
|
102
|
+
|
|
103
|
+
recovery = ppm.evaluate_ranking(ranking, train.truth, depths=(10, 20, 50))
|
|
104
|
+
prediction = ppm.evaluate_prediction(
|
|
105
|
+
test.outcome.values,
|
|
106
|
+
model.predict_proba(test.X)[:, 1],
|
|
107
|
+
metric="roc_auc",
|
|
108
|
+
)
|
|
109
|
+
print(recovery.at_depth[0].recall)
|
|
110
|
+
print(prediction.value)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Recall measures how many of the three generating features appear in the model's top ten;
|
|
114
|
+
AUC measures prediction on held-out observations. Repeat the comparison with different seeds
|
|
115
|
+
to estimate average performance and its Monte Carlo uncertainty. In 1.2.0,
|
|
116
|
+
`compare_paired_replicates` aligns procedures by world identity before computing
|
|
117
|
+
paired effects and explicitly counts failed or unmatched pairs. See
|
|
118
|
+
[paired evaluation](https://bradsegal.github.io/PyPlasmode/evaluation/#paired-procedure-comparisons).
|
|
119
|
+
|
|
120
|
+
Source rows are split before resampling, keeping training, validation and test
|
|
121
|
+
rows separate. This example uses a fixed model; the validation sample is available for
|
|
122
|
+
hyperparameter tuning. See [Methods](https://bradsegal.github.io/PyPlasmode/methods/) for the generation process.
|
|
123
|
+
|
|
124
|
+
## What can be varied
|
|
125
|
+
|
|
126
|
+
- **Signal:** null, sparse features, correlated groups, distributed modules, pairwise
|
|
127
|
+
interactions or a custom function.
|
|
128
|
+
- **Outcome:** binary probability, continuous mean and spread, count rate, ordinal category
|
|
129
|
+
probabilities or cumulative event risks at native-time horizons.
|
|
130
|
+
- **Evaluation:** feature and group recovery, weighted-module and graph-region coverage,
|
|
131
|
+
signal reconstruction, prediction accuracy, panel performance and ranking stability.
|
|
132
|
+
Additional functions handle tied scores, incomplete selections and matched chance references.
|
|
133
|
+
|
|
134
|
+
Start with [Methods](https://bradsegal.github.io/PyPlasmode/methods/), then choose a [signal](https://bradsegal.github.io/PyPlasmode/truths/) and
|
|
135
|
+
[outcome](https://bradsegal.github.io/PyPlasmode/outcomes/). The [evaluation guide](https://bradsegal.github.io/PyPlasmode/evaluation/) defines each result.
|
|
136
|
+
The [validation example](https://bradsegal.github.io/PyPlasmode/validation/) checks the generators and recovery metrics against
|
|
137
|
+
known values and reference estimators.
|
|
138
|
+
|
|
139
|
+
The executable [quickstart](https://github.com/BradSegal/PyPlasmode/blob/main/examples/quickstart.py),
|
|
140
|
+
[survival example](https://github.com/BradSegal/PyPlasmode/blob/main/examples/survival.py)
|
|
141
|
+
and [custom signal](https://github.com/BradSegal/PyPlasmode/blob/main/examples/custom_signal.py)
|
|
142
|
+
use synthetic inputs. The [documentation](https://bradsegal.github.io/PyPlasmode/)
|
|
143
|
+
includes the generation methods, evaluation definitions and API reference.
|
|
144
|
+
|
|
145
|
+
## Working with empirical data
|
|
146
|
+
|
|
147
|
+
Resampling preserves the source data's feature relationships and missingness patterns.
|
|
148
|
+
It can reproduce individual records, so generated datasets require the same privacy protections
|
|
149
|
+
as their source. See [Data handling](https://bradsegal.github.io/PyPlasmode/privacy-and-limitations/).
|
|
150
|
+
|
|
151
|
+
## Licence
|
|
152
|
+
|
|
153
|
+
PyPlasmode uses the [BSD 3-Clause License](https://github.com/BradSegal/PyPlasmode/blob/main/LICENSE).
|
|
154
|
+
|
|
155
|
+
## New in 1.2.0
|
|
156
|
+
|
|
157
|
+
Version 1.2.0 extends generation and evaluation. `pyplasmode.metrics` adds chance-corrected
|
|
158
|
+
selection stability, tie-aware RBO, consensus ranks, censored cumulative/dynamic
|
|
159
|
+
AUROC, paired participant bootstrap differences and discrimination gaps. Binary
|
|
160
|
+
and survival specifications accept `target_auc` and report the solved effect
|
|
161
|
+
and expected oracle AUROC. See [metrics](https://bradsegal.github.io/PyPlasmode/metrics/),
|
|
162
|
+
[calibration](https://bradsegal.github.io/PyPlasmode/calibration/) and the [worked example](https://github.com/BradSegal/PyPlasmode/blob/main/examples/metrics_calibration.py).
|
|
163
|
+
|
|
164
|
+
The [synthetic benchmark](https://bradsegal.github.io/PyPlasmode/benchmark-demo/) fits Cox, ridge and XGBoost
|
|
165
|
+
across three signal mechanisms and three target oracle AUROCs, then compares
|
|
166
|
+
held-out AUROC with chance-adjusted top-20 recovery. Install the optional
|
|
167
|
+
`benchmark` extra to run `python examples/benchmark_recovery_demo.py`.
|
|
168
|
+
Install it with `python -m pip install "pyplasmode[benchmark]"`.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# PyPlasmode
|
|
2
|
+
|
|
3
|
+
PyPlasmode tests prediction and feature selection against a known signal. It combines your
|
|
4
|
+
feature data with simulated outcomes, so you can measure both how accurately a model predicts
|
|
5
|
+
and how well it identifies the features used to generate those outcomes.
|
|
6
|
+
|
|
7
|
+
Built for biomarker benchmarking, the library works with numeric tabular data more generally.
|
|
8
|
+
Its inputs are a matrix of observations and named features, not a particular assay or dataset.
|
|
9
|
+
A plasmode uses observed measurements to retain realistic feature relationships while you
|
|
10
|
+
control the outcome-generating mechanism. Synthetic matrices work too, as in the examples below.
|
|
11
|
+
|
|
12
|
+
Use it with your existing modelling workflow: generate a dataset, fit and tune your models,
|
|
13
|
+
then evaluate their predictions, feature rankings or selected panels.
|
|
14
|
+
|
|
15
|
+
[](https://bradsegal.github.io/PyPlasmode/methods/)
|
|
16
|
+
|
|
17
|
+
The same generated dataset supports two comparisons: predictions against outcomes, and
|
|
18
|
+
selected features against the signal that generated them.
|
|
19
|
+
|
|
20
|
+
Explore the [visual gallery](https://bradsegal.github.io/PyPlasmode/gallery/) for signal patterns, correlated-feature recovery
|
|
21
|
+
and the five outcome families.
|
|
22
|
+
|
|
23
|
+
The [feature-selection tutorial](https://bradsegal.github.io/PyPlasmode/tutorial/) follows a complete example from correlated
|
|
24
|
+
measurements through model fitting, attribution and recovery above chance.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Requires Python 3.12-3.15. Install PyPlasmode 1.2.0 from [PyPI](https://pypi.org/project/pyplasmode/):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m pip install pyplasmode
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The runtime dependencies are NumPy, SciPy and scikit-learn. To include the
|
|
35
|
+
reference validation and survival examples:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python -m pip install "pyplasmode[validation]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Ten-minute example
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import numpy as np
|
|
45
|
+
from sklearn.linear_model import LogisticRegression
|
|
46
|
+
import pyplasmode as ppm
|
|
47
|
+
|
|
48
|
+
rng = np.random.default_rng(1)
|
|
49
|
+
population = ppm.Population(
|
|
50
|
+
rng.normal(size=(2_000, 100)),
|
|
51
|
+
tuple(f"feature_{index}" for index in range(100)),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
partition = ppm.partition_population(
|
|
55
|
+
population,
|
|
56
|
+
training_fraction=0.6,
|
|
57
|
+
validation_fraction=0.2,
|
|
58
|
+
seed=7,
|
|
59
|
+
)
|
|
60
|
+
samples = ppm.generate_partitioned(
|
|
61
|
+
partition,
|
|
62
|
+
truth=ppm.SparseTruth(features=("feature_3", "feature_17", "feature_42")),
|
|
63
|
+
outcome=ppm.BinaryOutcome(probability=0.15, odds_ratio=2.0),
|
|
64
|
+
sample_sizes=ppm.PartitionSampleSizes(1_200, 400, 400),
|
|
65
|
+
sampling_method="without_replacement",
|
|
66
|
+
seed=42,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
train, test = samples.training, samples.evaluation
|
|
70
|
+
model = LogisticRegression(max_iter=1_000).fit(train.X, train.outcome.values)
|
|
71
|
+
ranking = tuple(train.feature_ids[index] for index in np.argsort(-np.abs(model.coef_[0])))
|
|
72
|
+
|
|
73
|
+
recovery = ppm.evaluate_ranking(ranking, train.truth, depths=(10, 20, 50))
|
|
74
|
+
prediction = ppm.evaluate_prediction(
|
|
75
|
+
test.outcome.values,
|
|
76
|
+
model.predict_proba(test.X)[:, 1],
|
|
77
|
+
metric="roc_auc",
|
|
78
|
+
)
|
|
79
|
+
print(recovery.at_depth[0].recall)
|
|
80
|
+
print(prediction.value)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Recall measures how many of the three generating features appear in the model's top ten;
|
|
84
|
+
AUC measures prediction on held-out observations. Repeat the comparison with different seeds
|
|
85
|
+
to estimate average performance and its Monte Carlo uncertainty. In 1.2.0,
|
|
86
|
+
`compare_paired_replicates` aligns procedures by world identity before computing
|
|
87
|
+
paired effects and explicitly counts failed or unmatched pairs. See
|
|
88
|
+
[paired evaluation](https://bradsegal.github.io/PyPlasmode/evaluation/#paired-procedure-comparisons).
|
|
89
|
+
|
|
90
|
+
Source rows are split before resampling, keeping training, validation and test
|
|
91
|
+
rows separate. This example uses a fixed model; the validation sample is available for
|
|
92
|
+
hyperparameter tuning. See [Methods](https://bradsegal.github.io/PyPlasmode/methods/) for the generation process.
|
|
93
|
+
|
|
94
|
+
## What can be varied
|
|
95
|
+
|
|
96
|
+
- **Signal:** null, sparse features, correlated groups, distributed modules, pairwise
|
|
97
|
+
interactions or a custom function.
|
|
98
|
+
- **Outcome:** binary probability, continuous mean and spread, count rate, ordinal category
|
|
99
|
+
probabilities or cumulative event risks at native-time horizons.
|
|
100
|
+
- **Evaluation:** feature and group recovery, weighted-module and graph-region coverage,
|
|
101
|
+
signal reconstruction, prediction accuracy, panel performance and ranking stability.
|
|
102
|
+
Additional functions handle tied scores, incomplete selections and matched chance references.
|
|
103
|
+
|
|
104
|
+
Start with [Methods](https://bradsegal.github.io/PyPlasmode/methods/), then choose a [signal](https://bradsegal.github.io/PyPlasmode/truths/) and
|
|
105
|
+
[outcome](https://bradsegal.github.io/PyPlasmode/outcomes/). The [evaluation guide](https://bradsegal.github.io/PyPlasmode/evaluation/) defines each result.
|
|
106
|
+
The [validation example](https://bradsegal.github.io/PyPlasmode/validation/) checks the generators and recovery metrics against
|
|
107
|
+
known values and reference estimators.
|
|
108
|
+
|
|
109
|
+
The executable [quickstart](https://github.com/BradSegal/PyPlasmode/blob/main/examples/quickstart.py),
|
|
110
|
+
[survival example](https://github.com/BradSegal/PyPlasmode/blob/main/examples/survival.py)
|
|
111
|
+
and [custom signal](https://github.com/BradSegal/PyPlasmode/blob/main/examples/custom_signal.py)
|
|
112
|
+
use synthetic inputs. The [documentation](https://bradsegal.github.io/PyPlasmode/)
|
|
113
|
+
includes the generation methods, evaluation definitions and API reference.
|
|
114
|
+
|
|
115
|
+
## Working with empirical data
|
|
116
|
+
|
|
117
|
+
Resampling preserves the source data's feature relationships and missingness patterns.
|
|
118
|
+
It can reproduce individual records, so generated datasets require the same privacy protections
|
|
119
|
+
as their source. See [Data handling](https://bradsegal.github.io/PyPlasmode/privacy-and-limitations/).
|
|
120
|
+
|
|
121
|
+
## Licence
|
|
122
|
+
|
|
123
|
+
PyPlasmode uses the [BSD 3-Clause License](https://github.com/BradSegal/PyPlasmode/blob/main/LICENSE).
|
|
124
|
+
|
|
125
|
+
## New in 1.2.0
|
|
126
|
+
|
|
127
|
+
Version 1.2.0 extends generation and evaluation. `pyplasmode.metrics` adds chance-corrected
|
|
128
|
+
selection stability, tie-aware RBO, consensus ranks, censored cumulative/dynamic
|
|
129
|
+
AUROC, paired participant bootstrap differences and discrimination gaps. Binary
|
|
130
|
+
and survival specifications accept `target_auc` and report the solved effect
|
|
131
|
+
and expected oracle AUROC. See [metrics](https://bradsegal.github.io/PyPlasmode/metrics/),
|
|
132
|
+
[calibration](https://bradsegal.github.io/PyPlasmode/calibration/) and the [worked example](https://github.com/BradSegal/PyPlasmode/blob/main/examples/metrics_calibration.py).
|
|
133
|
+
|
|
134
|
+
The [synthetic benchmark](https://bradsegal.github.io/PyPlasmode/benchmark-demo/) fits Cox, ridge and XGBoost
|
|
135
|
+
across three signal mechanisms and three target oracle AUROCs, then compares
|
|
136
|
+
held-out AUROC with chance-adjusted top-20 recovery. Install the optional
|
|
137
|
+
`benchmark` extra to run `python examples/benchmark_recovery_demo.py`.
|
|
138
|
+
Install it with `python -m pip install "pyplasmode[benchmark]"`.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# API reference
|
|
2
2
|
|
|
3
3
|
The common entry points are exported from `pyplasmode`. Matrices have shape
|
|
4
|
-
`(
|
|
4
|
+
`(observations, features)`; `feature_ids` supplies one unique string per column.
|
|
5
5
|
Generated arrays are read-only. Convert result dataclasses to plain records with
|
|
6
6
|
`dataclasses.asdict` for your reporting workflow.
|
|
7
7
|
|
|
8
8
|
Use `generate` for one sample or `partition_population` followed by
|
|
9
|
-
`generate_partitioned`
|
|
9
|
+
`generate_partitioned` to keep source rows separate between training, validation and test. Access
|
|
10
10
|
`outcome.values` for binary, continuous and count outcomes, `outcome.codes` for
|
|
11
11
|
ordinal outcomes, and `outcome.time` plus `outcome.event` for survival outcomes.
|
|
12
12
|
Survival times are in days; latent event and censoring times beyond administrative
|
|
13
13
|
follow-up are represented by infinity.
|
|
14
14
|
|
|
15
|
-
The
|
|
16
|
-
methods and evaluation
|
|
15
|
+
The reference below lists signatures, types and result fields. See
|
|
16
|
+
[Methods](methods.md) for generation and [Evaluation](evaluation.md) for scoring.
|
|
17
17
|
|
|
18
18
|
## Populations and sampling
|
|
19
19
|
|
|
@@ -39,10 +39,17 @@ methods and evaluation guides for the scientific definitions.
|
|
|
39
39
|
|
|
40
40
|
::: pyplasmode.evaluate
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## Tied selections and matched references
|
|
43
|
+
|
|
44
|
+
The `nominations` module represents selections with membership weights, so tied scores
|
|
45
|
+
can share a place in a leading list. It also compares recovery with matched random selection.
|
|
43
46
|
|
|
44
47
|
::: pyplasmode.nominations
|
|
45
48
|
|
|
46
49
|
## Evaluation results
|
|
47
50
|
|
|
48
51
|
::: pyplasmode.results
|
|
52
|
+
|
|
53
|
+
## Selection agreement and censored discrimination
|
|
54
|
+
|
|
55
|
+
::: pyplasmode.metrics
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Concept diagrams
|
|
2
|
+
|
|
3
|
+
These diagrams illustrate outcome generation and feature recovery in PyPlasmode.
|
|
4
|
+
The repository retains the vector PDFs for editing and print use; the documentation
|
|
5
|
+
uses PNG previews. The source distribution includes the previews.
|
|
6
|
+
|
|
7
|
+
- `plasmode-workflow`: observed features, generating signal, outcomes and evaluation.
|
|
8
|
+
- `signal-recovery`: generating contributors and features selected by a fitted model.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Prediction and biomarker recovery on synthetic data
|
|
2
|
+
|
|
3
|
+
[`examples/benchmark_recovery_demo.py`](https://github.com/BradSegal/PyPlasmode/blob/main/examples/benchmark_recovery_demo.py)
|
|
4
|
+
is a small, self-contained comparison of Cox, ridge and XGBoost. It generates
|
|
5
|
+
all features and outcomes in memory; it requires no participant data or downloads.
|
|
6
|
+
|
|
7
|
+
On macOS, install the OpenMP runtime required by XGBoost with
|
|
8
|
+
`brew install libomp`. Then install the optional model dependencies and download
|
|
9
|
+
the linked example:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python -m pip install "pyplasmode[benchmark]"
|
|
13
|
+
python benchmark_recovery_demo.py --output benchmark-results.csv
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The core package does not require scikit-survival or XGBoost. The extra is
|
|
17
|
+
tested on Python 3.12–3.15 on Linux, with Windows and macOS covered by CI on
|
|
18
|
+
Python 3.13; installation depends on the model libraries' platform support.
|
|
19
|
+
|
|
20
|
+
The default is 3,000 rows and 120 features, with one numerical thread. Allow a
|
|
21
|
+
few minutes on a laptop CPU. `--rows 1000` provides a smaller demonstration;
|
|
22
|
+
`--seed` sets the random seed (default 42). The final console table and optional
|
|
23
|
+
CSV contain all 27 comparisons: three mechanisms, three effect sizes and three
|
|
24
|
+
methods. The CSV retains full precision, expected and realised oracle AUROC,
|
|
25
|
+
the recovery definition and the achieved nomination size.
|
|
26
|
+
|
|
27
|
+
## What is compared
|
|
28
|
+
|
|
29
|
+
Features are Gaussian, arranged in 30 independent blocks of four. Features in
|
|
30
|
+
the same block have population correlation 0.7. There are three signals:
|
|
31
|
+
|
|
32
|
+
| Signal | Generating mechanism | Recovery at depth 20 |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| Sparse | Eight direct features, one in each of eight blocks | Fraction of direct features selected |
|
|
35
|
+
| Correlated | Eight direct sentinels, each with three admissible correlated substitutes | Fraction of groups represented by at least one selected member |
|
|
36
|
+
| Distributed | Equal weights on the first 60 features | Fraction of absolute generating weight selected |
|
|
37
|
+
|
|
38
|
+
The sparse and correlated signals use the same generating features. Their
|
|
39
|
+
different recovery definitions show how allowing correlated substitutes changes
|
|
40
|
+
the question being answered. A 20-feature panel cannot cover all 60 distributed
|
|
41
|
+
features; its maximum unadjusted weight coverage is one third. Recovery columns
|
|
42
|
+
therefore measure each stated mechanism and should be interpreted within it.
|
|
43
|
+
|
|
44
|
+
`TimeToEventOutcome(target_auc=...)` sets expected ten-year oracle AUROC to
|
|
45
|
+
0.60, 0.70 and 0.80 in the finite synthetic covariate population. Ten-year event
|
|
46
|
+
risk is 0.20 and eleven-year risk is 0.22. Follow-up ends at eleven years,
|
|
47
|
+
with no earlier censoring. Calibration precedes outcome sampling, so realised
|
|
48
|
+
held-out oracle AUROC varies around the requested value. Common random draws
|
|
49
|
+
are used across settings.
|
|
50
|
+
|
|
51
|
+
A fixed 60:40 split separates training and evaluation. Scaling is learnt on
|
|
52
|
+
training features only. Cox uses all observed event times with L2 penalty 1.
|
|
53
|
+
Ridge uses ten-year event indicators with penalty 10. XGBoost uses the same
|
|
54
|
+
indicators, 80 depth-two trees, learning rate 0.05 and one CPU thread. There is
|
|
55
|
+
no hyperparameter search. For ridge and XGBoost the horizon labels are fully
|
|
56
|
+
observed because there is no early censoring; this construction would need to
|
|
57
|
+
change if early censoring were introduced.
|
|
58
|
+
|
|
59
|
+
## Reading the output
|
|
60
|
+
|
|
61
|
+
AUROC is the ten-year cumulative/dynamic estimate from
|
|
62
|
+
`pyplasmode.metrics.cumulative_dynamic_auc`, using the training censoring
|
|
63
|
+
distribution. Higher scores mean higher risk. With the chosen follow-up this
|
|
64
|
+
equals ordinary binary AUROC at ten years.
|
|
65
|
+
|
|
66
|
+
Absolute standardised coefficients rank Cox and ridge features; XGBoost uses
|
|
67
|
+
gain importance. `nomination_from_scores` allocates boundary ties fractionally
|
|
68
|
+
and does not fill unused slots with zero-importance features. `Top20` reports
|
|
69
|
+
the achieved size. `evaluate_matched_recovery` compares each nomination to
|
|
70
|
+
truth-blind random nominations with the same size, positive support and tie
|
|
71
|
+
geometry, within one common feature stratum. All blocks have the same size and
|
|
72
|
+
population correlation in this example.
|
|
73
|
+
|
|
74
|
+
`Adjusted` is **observed minus expected chance recovery**, without rescaling
|
|
75
|
+
or clipping. Negative values are possible. `Target` is the expected oracle AUROC requested and achieved by calibration.
|
|
76
|
+
The `Oracle` column is the realised
|
|
77
|
+
held-out AUROC of the generating signal. Compare fitted AUROC and recovery
|
|
78
|
+
across the three targets: stronger discrimination need not yield an equally
|
|
79
|
+
large change in recovery. This is one synthetic split with fixed model settings,
|
|
80
|
+
so its table demonstrates the evaluation rather than establishing a method ranking.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Calibrating oracle AUROC
|
|
2
|
+
|
|
3
|
+
Use `target_auc` when the intended simulation difficulty is easier to specify
|
|
4
|
+
than an odds or hazard ratio. The generator solves a non-negative signal
|
|
5
|
+
coefficient, while preserving the requested marginal probability or event risks.
|
|
6
|
+
The resulting ratio is at least one per signal standard deviation.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pyplasmode as ppm
|
|
11
|
+
|
|
12
|
+
signal = np.random.default_rng(12).normal(size=20_000)
|
|
13
|
+
binary = ppm.generate_outcome(
|
|
14
|
+
ppm.BinaryOutcome(probability=0.1, target_auc=0.7),
|
|
15
|
+
signal,
|
|
16
|
+
np.random.default_rng(3),
|
|
17
|
+
)
|
|
18
|
+
assert abs(binary.calibration.oracle_auc - 0.7) < 1e-7
|
|
19
|
+
odds_ratio = binary.calibration["odds_ratio"]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For survival, set `auc_horizon` strictly before the administrative cutoff.
|
|
23
|
+
It defaults to the last event-risk point before the cutoff, or half the cutoff
|
|
24
|
+
when there is only one point. Interior horizons interpolate the cumulative
|
|
25
|
+
baseline hazard under the specified piecewise-constant rates. To target exactly
|
|
26
|
+
ten years with one risk point, set `administrative_horizon=ppm.years(11)`.
|
|
27
|
+
Survivors then remain under observation without additional events or censoring
|
|
28
|
+
between the final risk point and that later cutoff. Independent censoring changes the precision of a
|
|
29
|
+
censoring-aware estimate, but not the population oracle AUROC or solved hazard
|
|
30
|
+
ratio. All event and censoring risk points are still calibrated. The example
|
|
31
|
+
includes year 11 to retain follow-up beyond the ten-year AUROC horizon; unlike
|
|
32
|
+
setting only a later administrative cutoff, this also specifies additional
|
|
33
|
+
event and censoring risk between years 10 and 11.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
horizon = ppm.years(10)
|
|
37
|
+
survival = ppm.generate_outcome(
|
|
38
|
+
ppm.TimeToEventOutcome(
|
|
39
|
+
event_risks={horizon: 0.005, ppm.years(11): 0.006},
|
|
40
|
+
target_auc=0.72,
|
|
41
|
+
auc_horizon=horizon,
|
|
42
|
+
censoring_risks={horizon: 0.05, ppm.years(11): 0.055},
|
|
43
|
+
),
|
|
44
|
+
signal,
|
|
45
|
+
np.random.default_rng(3),
|
|
46
|
+
)
|
|
47
|
+
assert abs(survival.calibration.oracle_auc - 0.72) < 1e-7
|
|
48
|
+
hazard_ratio = survival.calibration["hazard_ratio"]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What is solved
|
|
52
|
+
|
|
53
|
+
The signal is standardised on the supplied rows. Binary outcomes use logistic
|
|
54
|
+
probabilities; survival outcomes use `1-exp(-H0(t)*exp(beta*z))`. For each trial
|
|
55
|
+
coefficient, the intercept or baseline hazard is recalibrated to the requested
|
|
56
|
+
marginal risk. Brent root-finding then matches the expected oracle AUROC.
|
|
57
|
+
|
|
58
|
+
The expectation integrates case/control pairs over the empirical signal
|
|
59
|
+
population, with half credit for ties. It uses independent draws from that
|
|
60
|
+
population, including draws of the same source row. It does not optimise a noisy
|
|
61
|
+
AUROC from one realised outcome sample. `CalibrationResult.oracle_auc` records
|
|
62
|
+
this achieved expectation; `target_auc` records the request. Realised AUROC will
|
|
63
|
+
vary with the seed, sample size, event count and censoring. The solved effect is
|
|
64
|
+
deterministic for a given signal and specification; repeating the generator
|
|
65
|
+
with the same seed also repeats the outcomes.
|
|
66
|
+
|
|
67
|
+
`generate_partitioned` calibrates once on the combined partition covariates,
|
|
68
|
+
so its returned samples share one solved effect. Calling `generate` separately
|
|
69
|
+
on training and test cohorts solves `target_auc` independently on each cohort.
|
|
70
|
+
Use `generate_partitioned` for a shared mechanism, or extract the solved ratio
|
|
71
|
+
with `train.outcome.calibration["odds_ratio"]` and pass it explicitly for test
|
|
72
|
+
outcomes. Their marginal baselines are still recalibrated separately.
|
|
73
|
+
|
|
74
|
+
`CalibrationResult.parameter_dict` returns a fresh parameter mapping;
|
|
75
|
+
`calibration["signal_coefficient"]` looks up one value. For protective effects
|
|
76
|
+
(`odds_ratio < 1` or `hazard_ratio < 1`), use the solved coefficient times the
|
|
77
|
+
standardised signal as the oracle risk score. Passing the unflipped signal
|
|
78
|
+
would reverse the score direction. `target_auc` always solves a non-negative
|
|
79
|
+
coefficient.
|
|
80
|
+
|
|
81
|
+
## Invalid and unattainable targets
|
|
82
|
+
|
|
83
|
+
Specify exactly one binary effect measure or `target_auc`, and exactly one
|
|
84
|
+
survival `hazard_ratio` or `target_auc`. A target must lie in `[0.5, 1)`; invalid
|
|
85
|
+
specifications raise `OutcomeSpecificationError`. A survival target requires
|
|
86
|
+
positive event risk below one at its evaluation horizon.
|
|
87
|
+
|
|
88
|
+
A target of 0.5 solves ratio one, including constant signals. A constant signal
|
|
89
|
+
cannot attain a higher target. Ties and the signal distribution can also limit
|
|
90
|
+
attainable discrimination. The solver supports log ratios from zero to eight,
|
|
91
|
+
independently of the largest signal value. Logistic probabilities use `expit`
|
|
92
|
+
and the intercept bracket expands to cover the supplied offsets. Survival
|
|
93
|
+
multipliers saturate at `exp(-700)` and `exp(700)` to keep extreme tail hazards
|
|
94
|
+
finite. No observations are dropped or winsorised. A request outside the
|
|
95
|
+
supported effect range or the signal's attainable discrimination raises
|
|
96
|
+
`CalibrationError`; it is never silently clipped. The target describes the
|
|
97
|
+
true generating predictor: a model fitted to finite training data will usually
|
|
98
|
+
have lower held-out AUROC.
|
|
99
|
+
|
|
100
|
+
## Validation
|
|
101
|
+
|
|
102
|
+
The statistical suite checks expected AUROC to absolute tolerance `1e-7`.
|
|
103
|
+
Generated binary AUROCs are checked within 0.025 across probabilities 0.02, 0.2
|
|
104
|
+
and 0.5 and targets 0.5, 0.6, 0.75 and 0.9, using 80,000 synthetic participants
|
|
105
|
+
per cell. Survival checks use 120,000 participants, event risks 0.005, 0.05 and
|
|
106
|
+
0.2, censoring risks 0, 0.05 and 0.3, and targets 0.5, 0.61, 0.72 and 0.79;
|
|
107
|
+
the realised IPCW tolerance is 0.035. These are reproducible simulation checks,
|
|
108
|
+
not a guarantee of this accuracy for a user's smaller sample.
|