structboost 0.1.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 (42) hide show
  1. structboost-0.1.0/.gitignore +107 -0
  2. structboost-0.1.0/CHANGELOG.md +32 -0
  3. structboost-0.1.0/CITATION.cff +9 -0
  4. structboost-0.1.0/CODE_OF_CONDUCT.md +8 -0
  5. structboost-0.1.0/CONTRIBUTING.md +65 -0
  6. structboost-0.1.0/LICENSE +21 -0
  7. structboost-0.1.0/PKG-INFO +219 -0
  8. structboost-0.1.0/README.md +147 -0
  9. structboost-0.1.0/conftest.py +35 -0
  10. structboost-0.1.0/pyproject.toml +131 -0
  11. structboost-0.1.0/src/structboost/__init__.py +94 -0
  12. structboost-0.1.0/src/structboost/_annotation.py +266 -0
  13. structboost-0.1.0/src/structboost/_boosting.py +552 -0
  14. structboost-0.1.0/src/structboost/_decoder.py +109 -0
  15. structboost-0.1.0/src/structboost/_encoder.py +88 -0
  16. structboost-0.1.0/src/structboost/_explorer.py +842 -0
  17. structboost-0.1.0/src/structboost/_io.py +302 -0
  18. structboost-0.1.0/src/structboost/_model.py +3483 -0
  19. structboost-0.1.0/src/structboost/_persistence.py +326 -0
  20. structboost-0.1.0/src/structboost/_plotting.py +301 -0
  21. structboost-0.1.0/src/structboost/_simulation.py +867 -0
  22. structboost-0.1.0/src/structboost/_stability.py +412 -0
  23. structboost-0.1.0/src/structboost/_types.py +393 -0
  24. structboost-0.1.0/src/structboost/_utils.py +509 -0
  25. structboost-0.1.0/src/structboost/py.typed +0 -0
  26. structboost-0.1.0/tests/test_allboost.py +540 -0
  27. structboost-0.1.0/tests/test_annotation.py +298 -0
  28. structboost-0.1.0/tests/test_bae.py +1631 -0
  29. structboost-0.1.0/tests/test_bae_diagnostics.py +334 -0
  30. structboost-0.1.0/tests/test_bae_init.py +284 -0
  31. structboost-0.1.0/tests/test_bae_layer.py +187 -0
  32. structboost-0.1.0/tests/test_bae_persistence.py +338 -0
  33. structboost-0.1.0/tests/test_bae_transfer.py +935 -0
  34. structboost-0.1.0/tests/test_batch_integration.py +175 -0
  35. structboost-0.1.0/tests/test_explorer.py +616 -0
  36. structboost-0.1.0/tests/test_linear_ceiling.py +77 -0
  37. structboost-0.1.0/tests/test_obs_encoding.py +172 -0
  38. structboost-0.1.0/tests/test_public_api.py +57 -0
  39. structboost-0.1.0/tests/test_resolve_mandatory.py +81 -0
  40. structboost-0.1.0/tests/test_rng_isolation.py +160 -0
  41. structboost-0.1.0/tests/test_simulation.py +618 -0
  42. structboost-0.1.0/tests/test_stability.py +746 -0
@@ -0,0 +1,107 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.pyd
4
+ .Python
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .eggs/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .coverage
13
+ htmlcov/
14
+ .ipynb_checkpoints/
15
+ .venv/
16
+ venv/
17
+ .DS_Store
18
+
19
+ # IDE
20
+ .vscode/
21
+ .idea/
22
+
23
+ # Environment
24
+ .env
25
+ *.env.local
26
+
27
+ # PyTorch
28
+ *.pt
29
+ *.pth
30
+ runs/
31
+ lightning_logs/
32
+
33
+ # Data (large files)
34
+ *.h5ad
35
+ data/
36
+ https:/
37
+
38
+ # Notebooks and scratch scripts. Kept local while a clean tutorial set is
39
+ # written: the existing notebooks store their outputs, so 96% of their bytes are
40
+ # base64 images and every re-execution added megabytes to history permanently.
41
+ examples/
42
+
43
+ # Agents. Skills stay local for now: they are tooling around the package, not
44
+ # part of it, and the package repo is being kept to what ships.
45
+ agent_instructions_logs/
46
+ agent_instructions/
47
+ CLAUDE.md
48
+ AGENTS.md
49
+ docs/plans/
50
+ .claude/
51
+ .codex/
52
+
53
+ # Agent transcript history
54
+ agent_transcript_history/
55
+
56
+ # Documentation build output. `docs/api/generated/` is autosummary output,
57
+ # regenerated on every build -- tracking it produced spurious diffs.
58
+ docs/_build/
59
+ docs/api/generated/
60
+ docs/generated/
61
+
62
+ # Julia code implementations
63
+ julia_componentwise_boosting_functions/
64
+ .juliaup_home/
65
+ .juliaup_home_test/
66
+ .julia_depot_test/
67
+ .julia_depot_bench_sandbox/
68
+ .julia_depot_nb/
69
+ .julia_depot/
70
+
71
+ # Benchmark outputs
72
+ benchmarks/bool_allboost/results/*.csv
73
+ benchmarks/bool_allboost/figures/*.png
74
+
75
+ # Numba JIT cache artifacts (v8 uses @njit(cache=True))
76
+ benchmarks/bool_allboost/versions/**/*.nbi
77
+ benchmarks/bool_allboost/versions/**/*.nbc
78
+
79
+ # Cython build artifacts for bool_allboost v9
80
+ benchmarks/bool_allboost/versions/_v9_kernel.c
81
+ benchmarks/bool_allboost/versions/_v9_kernel*.so
82
+ benchmarks/bool_allboost/versions/build/
83
+
84
+ # Manuscript work. Separate lifecycle from the package; belongs in its own repo.
85
+ paper_analysis/
86
+ PAPER_PLAN.md
87
+
88
+ # Design notes for work that is not implemented yet. Publishing them would
89
+ # advertise behaviour the package does not have; move into docs/ when it lands.
90
+ SCALING.md
91
+ BAE_TARGET_SCALING.md
92
+
93
+ # Binary-predictor boosting, held back from the initial package. It is the
94
+ # intended encoder fitter for a planned Bernoulli BAE variant and is not wired
95
+ # into BAE.fit, so it ships with nothing and is kept local until that lands.
96
+ src/structboost/_bool_boosting.py
97
+ tests/test_bool_boosting.py
98
+
99
+ # Micro-optimization experiments for it, kept out of the package repo too.
100
+ benchmarks/
101
+ # Their equivalence tests go with them. Kept on disk so the local benchmark
102
+ # workflow still runs; they import benchmarks/ and cannot pass in a fresh clone.
103
+ tests/benchmarks/
104
+ tests/test_bool_allboost_shared.py
105
+ tests/test_bool_allboost_bench_equivalence.py
106
+ # Loads .codex/skills/... by path, so it goes wherever the skills go.
107
+ tests/test_explore_bae_results_skill.py
@@ -0,0 +1,32 @@
1
+ ## Changelog
2
+
3
+ Releases follow [semantic versioning](https://semver.org). While the project is
4
+ pre-1.0, a minor bump may break API.
5
+
6
+ ### [0.1.0] - 2026-07-31
7
+
8
+ First public release.
9
+
10
+ Batch integration is one argument. `BAE.fit(batch_key=...)` names the covariate,
11
+ following scVI's spelling, and `batch_integration_mode` chooses between
12
+ `"decoder"`, `"encoder"` and `"both"`, defaulting to `"both"`. No `batch_key`
13
+ means no integration, and naming a mode without one raises rather than quietly
14
+ integrating nothing.
15
+
16
+ `"encoder"` names the half of the model the mechanism protects, not a tensor the
17
+ covariate is fed to: it enters the boosting design as a mandatory regressor so
18
+ gene selection is not confounded by it. `transform` remains gene-only and needs
19
+ no covariate labels under any mode.
20
+
21
+ The ridge that stabilizes near-collinear covariates is `BAEConfig.nuisance_ridge`.
22
+ It is a numerical knob rather than a modelling one, so it sits with the other
23
+ algorithm settings.
24
+
25
+ The `test` extra pulls the runtime dependencies. The sdist carries `tests/` and
26
+ `conftest.py` so that downstream packagers can run the suite at build time, and
27
+ with pytest alone that did not work: 360 of the 411 test functions sit behind an
28
+ `importorskip` for torch or anndata, so `pip install .[test] && pytest` ran ~51
29
+ tests, skipped the rest and reported success. Installing `[test]` now brings in
30
+ `[bae]`, so a green build means the suite actually ran. Documenting the
31
+ requirement in `CONTRIBUTING.md` instead was rejected, because the reader who
32
+ needs it is an automated build script rather than a person.
@@ -0,0 +1,9 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use structboost in your research, please cite it."
3
+ title: "structboost"
4
+ type: software
5
+ license: MIT
6
+ authors:
7
+ - family-names: Brunn
8
+ given-names: Niklas
9
+ repository-code: "https://github.com/NiklasBrunn/structboost"
@@ -0,0 +1,8 @@
1
+ ## Code of Conduct
2
+
3
+ This project follows the Contributor Covenant (v2.1) in spirit: be respectful, assume good faith, and keep discussions constructive.
4
+
5
+ Unacceptable behavior includes harassment, discrimination, and sustained disruption of project discussions.
6
+
7
+ If an issue arises, please contact the maintainer(s) via a private channel (e.g. email or a direct message) and include relevant context.
8
+
@@ -0,0 +1,65 @@
1
+ ## Contributing
2
+
3
+ Thank you for your interest in contributing to `structboost`.
4
+
5
+ [Claude Code](https://claude.com/claude-code) (Anthropic) was used in building
6
+ this package, to support implementation, to write tests, and to write the
7
+ documentation. Individual commits record it as a co-author. You are welcome to
8
+ use coding agents on a contribution. What is asked of a pull request is the same
9
+ either way: the change is reviewed by you before you open it, the tests pass, and
10
+ any behavioural claim added to a docstring is backed by a test or by a
11
+ measurement you can point to.
12
+
13
+ - **Bug reports / feature requests**: please open an issue with a minimal reproduction or clear proposal.
14
+ - **Pull requests**:
15
+ - Keep changes focused and add tests where feasible.
16
+ - Run locally:
17
+ - `ruff check .`
18
+ - `ruff format .`
19
+ - `pytest`
20
+
21
+ ### Development install
22
+
23
+ With [uv](https://docs.astral.sh/uv/), which is much faster and what CI uses:
24
+
25
+ ```bash
26
+ uv venv
27
+ uv pip install -e ".[bae,plot,dev,test,docs]"
28
+ uv run pre-commit install
29
+ ```
30
+
31
+ Or with plain pip, which is fully supported:
32
+
33
+ ```bash
34
+ python -m pip install -U pip
35
+ python -m pip install -e ".[dev,test]"
36
+ pre-commit install
37
+ ```
38
+
39
+ Either way the package builds with Hatchling and end users install it from PyPI
40
+ with pip. uv is only a developer convenience. A committed `uv.lock` pins the
41
+ CI/development environment for reproducibility. It does not constrain the
42
+ dependency ranges that downstream users resolve against. After changing
43
+ dependencies in `pyproject.toml`, refresh it with `uv lock` and commit the
44
+ result.
45
+
46
+ ### Versioning and releases
47
+
48
+ Releases follow [semantic versioning](https://semver.org): `MAJOR.MINOR.PATCH`.
49
+
50
+ While the project is pre-1.0, **a minor bump may break API**. The version
51
+ communicates the size of a change, not a compatibility promise. `1.0.0` will
52
+ mark the point at which that promise begins.
53
+
54
+ Every user-facing change (new feature, bug fix, behavioural change) needs:
55
+
56
+ 1. A `CHANGELOG.md` entry under the correct `### [MAJOR.MINOR.PATCH]` heading,
57
+ in the house style, meaning a bold sentence naming the symbol, then *why*, including
58
+ any measurements and rejected alternatives, and a **Migration:** note for
59
+ anything breaking.
60
+ 2. A matching `version` bump in `pyproject.toml`.
61
+
62
+ Releases are cut by pushing a tag that matches `pyproject.toml` exactly
63
+ (`git tag v0.1.0 && git push --tags`). `.github/workflows/release.yml` verifies
64
+ the two agree and refuses to publish if they do not.
65
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 niklas-br
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,219 @@
1
+ Metadata-Version: 2.4
2
+ Name: structboost
3
+ Version: 0.1.0
4
+ Summary: Boosting Autoencoders (BAE) and componentwise L2 boosting utilities for scverse-style workflows.
5
+ Project-URL: Homepage, https://github.com/NiklasBrunn/structboost
6
+ Project-URL: Repository, https://github.com/NiklasBrunn/structboost
7
+ Project-URL: Issues, https://github.com/NiklasBrunn/structboost/issues
8
+ Author: Niklas Brunn
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 niklas-br
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: autoencoder,boosting,dimensionality-reduction,gene-selection,scverse,single-cell
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3 :: Only
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
41
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
42
+ Requires-Python: >=3.10
43
+ Requires-Dist: numpy>=1.23
44
+ Provides-Extra: bae
45
+ Requires-Dist: anndata>=0.10; extra == 'bae'
46
+ Requires-Dist: pandas>=1.5; extra == 'bae'
47
+ Requires-Dist: scipy>=1.10; extra == 'bae'
48
+ Requires-Dist: torch>=2; extra == 'bae'
49
+ Requires-Dist: tqdm>=4.60; extra == 'bae'
50
+ Provides-Extra: dev
51
+ Requires-Dist: pre-commit>=3.6; extra == 'dev'
52
+ Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
53
+ Provides-Extra: docs
54
+ Requires-Dist: furo>=2024.1.29; extra == 'docs'
55
+ Requires-Dist: myst-parser>=2.0; extra == 'docs'
56
+ Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
57
+ Requires-Dist: sphinx-design>=0.5; extra == 'docs'
58
+ Requires-Dist: sphinx>=7.2; extra == 'docs'
59
+ Provides-Extra: io
60
+ Requires-Dist: pandas>=1.5; extra == 'io'
61
+ Requires-Dist: pyarrow>=12; extra == 'io'
62
+ Provides-Extra: plot
63
+ Requires-Dist: matplotlib>=3.7; extra == 'plot'
64
+ Provides-Extra: test
65
+ Requires-Dist: anndata>=0.10; extra == 'test'
66
+ Requires-Dist: pandas>=1.5; extra == 'test'
67
+ Requires-Dist: pytest>=7.4; extra == 'test'
68
+ Requires-Dist: scipy>=1.10; extra == 'test'
69
+ Requires-Dist: torch>=2; extra == 'test'
70
+ Requires-Dist: tqdm>=4.60; extra == 'test'
71
+ Description-Content-Type: text/markdown
72
+
73
+ # structboost
74
+
75
+ > **Note that this package is under active development.** The API is still
76
+ > moving, and a minor version bump may break it.
77
+
78
+ **Structured representation learning for single-cell data. A latent space you can
79
+ read gene by gene.**
80
+
81
+ The **Boosting Autoencoder (BAE)** pairs a linear encoder fitted by componentwise
82
+ L2 boosting with an MLP decoder trained by gradient descent. Each training
83
+ iteration takes a gradient step on the latent code itself and hands the result to
84
+ the boosting fit as a regression target, so the encoder is fitted against the
85
+ negative gradient of the reconstruction loss rather than by backpropagation.
86
+ Componentwise boosting adds one gene at a time and shrinks each step, which keeps
87
+ the encoder weights sparse by construction rather than by a post-hoc threshold.
88
+
89
+ Each latent dimension is therefore a short, signed gene list, and `X_bae` is
90
+ exactly `X @ varm["BAE_encoder_weights"]`.
91
+
92
+ The package also ships `allboost`, the componentwise boosting routine on its own,
93
+ for sparse supervised problems with no autoencoder involved.
94
+
95
+ ## Relation to the original method
96
+
97
+ This is a scanpy-compatible Python re-implementation of the Boosting Autoencoder
98
+ introduced in [Hackenberg et al. (2025)](https://doi.org/10.1038/s42003-025-07872-9),
99
+ where the method and its componentwise boosting core were developed in Julia.
100
+
101
+ **Some methodological components differ from the original proposal.** Defaults
102
+ and several parts of the training procedure were re-derived here against
103
+ simulated data with known ground truth, and the measurements behind each are
104
+ recorded in the [user guide](https://niklasbrunn.github.io/structboost/guide/index.html)
105
+ next to the setting they justify. Results from this implementation should
106
+ therefore not be assumed identical to the original paper's.
107
+
108
+ 📖 **[Documentation](https://niklasbrunn.github.io/structboost)** ·
109
+ [User guide](https://niklasbrunn.github.io/structboost/guide/index.html) ·
110
+ [API reference](https://niklasbrunn.github.io/structboost/api/index.html) ·
111
+ [Changelog](CHANGELOG.md)
112
+
113
+ ## Installation
114
+
115
+ Requires Python 3.10 or newer. The core depends on NumPy alone, and everything
116
+ heavier is opt-in.
117
+
118
+ ```bash
119
+ pip install "structboost[bae,plot]" # the BAE
120
+ pip install structboost # allboost only, NumPy-only
121
+ ```
122
+
123
+ | Extra | Brings in | Needed for |
124
+ | --- | --- | --- |
125
+ | *(none)* | numpy | `allboost`, `stability_selection` |
126
+ | `bae` | torch, anndata, scipy, pandas, tqdm | `BAE`, the simulator, everything AnnData |
127
+ | `plot` | matplotlib | the `plot_*` functions |
128
+ | `io` | pyarrow | Parquet encoder-weight files |
129
+
130
+ Not on PyPI yet. Until it is, install from source or from TestPyPI. Pin the
131
+ version: TestPyPI also carries older pre-release builds under this name, and an
132
+ unpinned install resolves to one of those rather than to the current code.
133
+
134
+ ```bash
135
+ pip install --index-url https://test.pypi.org/simple/ \
136
+ --extra-index-url https://pypi.org/simple/ "structboost[bae]==0.1.0"
137
+ ```
138
+
139
+ See [Installation](https://niklasbrunn.github.io/structboost/installation.html)
140
+ for the from-source and development setups.
141
+
142
+ ## Quickstart
143
+
144
+ `adata.X` must be z-scored, which `sc.pp.scale` gives you.
145
+
146
+ ```python
147
+ from structboost import BAE, BAEConfig
148
+
149
+ model = BAE(adata.n_vars, BAEConfig(latent_dim=10))
150
+ model.fit(adata)
151
+
152
+ adata.obsm["X_bae"] # (n_cells, 10) latent space
153
+ adata.varm["BAE_encoder_weights"] # (n_genes, 10), sparse
154
+ ```
155
+
156
+ A single fit gives one gene list, and that list is **not reproducible**. In a
157
+ high-dimensional feature space with strongly correlated genes the encoder support
158
+ is not identifiable: many different sparse gene sets reconstruct the data about
159
+ equally well, and a fit returns one of them.
160
+
161
+ ```python
162
+ res = model.stability_selection(adata, mode="iteration")
163
+ genes = [adata.var_names[res.stable_support[:, j]] for j in range(res.frequency.shape[1])]
164
+ ```
165
+
166
+ Componentwise L2 boosting on its own, no autoencoder involved:
167
+
168
+ ```python
169
+ from structboost import allboost
170
+
171
+ betamat = allboost(sourcemat, targetmat_std, stepno=20, nu=0.1)
172
+ # (n_targets, n_features), sparse
173
+ ```
174
+
175
+ ## What else it does
176
+
177
+ Each of these has a guide page.
178
+
179
+ | | |
180
+ | --- | --- |
181
+ | [Batch integration](https://niklasbrunn.github.io/structboost/guide/tasks/batch-integration.html) | `batch_key` names the covariate and `batch_integration_mode` chooses whether it conditions the decoder, protects gene selection, or both. `transform` stays gene-only and needs no batch labels. |
182
+ | [Transfer](https://niklasbrunn.github.io/structboost/guide/tasks/transfer.html) | Carry a trained encoder matrix onto a new dataset with `from_reference`, aligned by gene name, with the prior programs frozen. |
183
+ | [Persistence](https://niklasbrunn.github.io/structboost/guide/tasks/persistence.html) | `save` and `load` a fitted model as one checkpoint, readable with `weights_only=True`. |
184
+ | [Interpretation](https://niklasbrunn.github.io/structboost/guide/tasks/interpreting.html) | Ranked gene lists per dimension, stored functional annotations, and a self-contained interactive HTML explorer. |
185
+ | [Simulation](https://niklasbrunn.github.io/structboost/guide/tasks/simulating.html) | Negative-binomial counts with planted gene programs and a cell-type hierarchy, so marker recovery can be scored against ground truth. |
186
+
187
+ ## Citation
188
+
189
+ If you use the **BAE**:
190
+
191
+ > Hackenberg, M., Brunn, N., Vogel, T. et al. *Infusing structural assumptions
192
+ > into dimensionality reduction for single-cell RNA sequencing data to identify
193
+ > small gene sets.* Commun Biol 8, 414 (2025).
194
+ > <https://doi.org/10.1038/s42003-025-07872-9>
195
+
196
+ If you use **allboost**:
197
+
198
+ > Binder, H., Schumacher, M. *Incorporating pathway information into boosting
199
+ > estimation of high-dimensional risk prediction models.* BMC Bioinformatics 10,
200
+ > 18 (2009). <https://doi.org/10.1186/1471-2105-10-18>
201
+
202
+ ## Development note
203
+
204
+ [Claude Code](https://claude.com/claude-code) (Anthropic) was used in building
205
+ this package, to support implementation, to write tests, and to write the
206
+ documentation. Individual commits record it as a co-author.
207
+
208
+ The methods, the design decisions, and the scientific claims are the authors'.
209
+ Everything committed was reviewed, and the behavioural claims in the docstrings
210
+ and the user guide are backed by the test suite or by the measurements cited
211
+ alongside them.
212
+
213
+ ## Contributing
214
+
215
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development setup, the versioning
216
+ policy, and what a pull request needs. Planned work is tracked in the
217
+ [issue tracker](https://github.com/NiklasBrunn/structboost/issues).
218
+
219
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,147 @@
1
+ # structboost
2
+
3
+ > **Note that this package is under active development.** The API is still
4
+ > moving, and a minor version bump may break it.
5
+
6
+ **Structured representation learning for single-cell data. A latent space you can
7
+ read gene by gene.**
8
+
9
+ The **Boosting Autoencoder (BAE)** pairs a linear encoder fitted by componentwise
10
+ L2 boosting with an MLP decoder trained by gradient descent. Each training
11
+ iteration takes a gradient step on the latent code itself and hands the result to
12
+ the boosting fit as a regression target, so the encoder is fitted against the
13
+ negative gradient of the reconstruction loss rather than by backpropagation.
14
+ Componentwise boosting adds one gene at a time and shrinks each step, which keeps
15
+ the encoder weights sparse by construction rather than by a post-hoc threshold.
16
+
17
+ Each latent dimension is therefore a short, signed gene list, and `X_bae` is
18
+ exactly `X @ varm["BAE_encoder_weights"]`.
19
+
20
+ The package also ships `allboost`, the componentwise boosting routine on its own,
21
+ for sparse supervised problems with no autoencoder involved.
22
+
23
+ ## Relation to the original method
24
+
25
+ This is a scanpy-compatible Python re-implementation of the Boosting Autoencoder
26
+ introduced in [Hackenberg et al. (2025)](https://doi.org/10.1038/s42003-025-07872-9),
27
+ where the method and its componentwise boosting core were developed in Julia.
28
+
29
+ **Some methodological components differ from the original proposal.** Defaults
30
+ and several parts of the training procedure were re-derived here against
31
+ simulated data with known ground truth, and the measurements behind each are
32
+ recorded in the [user guide](https://niklasbrunn.github.io/structboost/guide/index.html)
33
+ next to the setting they justify. Results from this implementation should
34
+ therefore not be assumed identical to the original paper's.
35
+
36
+ 📖 **[Documentation](https://niklasbrunn.github.io/structboost)** ·
37
+ [User guide](https://niklasbrunn.github.io/structboost/guide/index.html) ·
38
+ [API reference](https://niklasbrunn.github.io/structboost/api/index.html) ·
39
+ [Changelog](CHANGELOG.md)
40
+
41
+ ## Installation
42
+
43
+ Requires Python 3.10 or newer. The core depends on NumPy alone, and everything
44
+ heavier is opt-in.
45
+
46
+ ```bash
47
+ pip install "structboost[bae,plot]" # the BAE
48
+ pip install structboost # allboost only, NumPy-only
49
+ ```
50
+
51
+ | Extra | Brings in | Needed for |
52
+ | --- | --- | --- |
53
+ | *(none)* | numpy | `allboost`, `stability_selection` |
54
+ | `bae` | torch, anndata, scipy, pandas, tqdm | `BAE`, the simulator, everything AnnData |
55
+ | `plot` | matplotlib | the `plot_*` functions |
56
+ | `io` | pyarrow | Parquet encoder-weight files |
57
+
58
+ Not on PyPI yet. Until it is, install from source or from TestPyPI. Pin the
59
+ version: TestPyPI also carries older pre-release builds under this name, and an
60
+ unpinned install resolves to one of those rather than to the current code.
61
+
62
+ ```bash
63
+ pip install --index-url https://test.pypi.org/simple/ \
64
+ --extra-index-url https://pypi.org/simple/ "structboost[bae]==0.1.0"
65
+ ```
66
+
67
+ See [Installation](https://niklasbrunn.github.io/structboost/installation.html)
68
+ for the from-source and development setups.
69
+
70
+ ## Quickstart
71
+
72
+ `adata.X` must be z-scored, which `sc.pp.scale` gives you.
73
+
74
+ ```python
75
+ from structboost import BAE, BAEConfig
76
+
77
+ model = BAE(adata.n_vars, BAEConfig(latent_dim=10))
78
+ model.fit(adata)
79
+
80
+ adata.obsm["X_bae"] # (n_cells, 10) latent space
81
+ adata.varm["BAE_encoder_weights"] # (n_genes, 10), sparse
82
+ ```
83
+
84
+ A single fit gives one gene list, and that list is **not reproducible**. In a
85
+ high-dimensional feature space with strongly correlated genes the encoder support
86
+ is not identifiable: many different sparse gene sets reconstruct the data about
87
+ equally well, and a fit returns one of them.
88
+
89
+ ```python
90
+ res = model.stability_selection(adata, mode="iteration")
91
+ genes = [adata.var_names[res.stable_support[:, j]] for j in range(res.frequency.shape[1])]
92
+ ```
93
+
94
+ Componentwise L2 boosting on its own, no autoencoder involved:
95
+
96
+ ```python
97
+ from structboost import allboost
98
+
99
+ betamat = allboost(sourcemat, targetmat_std, stepno=20, nu=0.1)
100
+ # (n_targets, n_features), sparse
101
+ ```
102
+
103
+ ## What else it does
104
+
105
+ Each of these has a guide page.
106
+
107
+ | | |
108
+ | --- | --- |
109
+ | [Batch integration](https://niklasbrunn.github.io/structboost/guide/tasks/batch-integration.html) | `batch_key` names the covariate and `batch_integration_mode` chooses whether it conditions the decoder, protects gene selection, or both. `transform` stays gene-only and needs no batch labels. |
110
+ | [Transfer](https://niklasbrunn.github.io/structboost/guide/tasks/transfer.html) | Carry a trained encoder matrix onto a new dataset with `from_reference`, aligned by gene name, with the prior programs frozen. |
111
+ | [Persistence](https://niklasbrunn.github.io/structboost/guide/tasks/persistence.html) | `save` and `load` a fitted model as one checkpoint, readable with `weights_only=True`. |
112
+ | [Interpretation](https://niklasbrunn.github.io/structboost/guide/tasks/interpreting.html) | Ranked gene lists per dimension, stored functional annotations, and a self-contained interactive HTML explorer. |
113
+ | [Simulation](https://niklasbrunn.github.io/structboost/guide/tasks/simulating.html) | Negative-binomial counts with planted gene programs and a cell-type hierarchy, so marker recovery can be scored against ground truth. |
114
+
115
+ ## Citation
116
+
117
+ If you use the **BAE**:
118
+
119
+ > Hackenberg, M., Brunn, N., Vogel, T. et al. *Infusing structural assumptions
120
+ > into dimensionality reduction for single-cell RNA sequencing data to identify
121
+ > small gene sets.* Commun Biol 8, 414 (2025).
122
+ > <https://doi.org/10.1038/s42003-025-07872-9>
123
+
124
+ If you use **allboost**:
125
+
126
+ > Binder, H., Schumacher, M. *Incorporating pathway information into boosting
127
+ > estimation of high-dimensional risk prediction models.* BMC Bioinformatics 10,
128
+ > 18 (2009). <https://doi.org/10.1186/1471-2105-10-18>
129
+
130
+ ## Development note
131
+
132
+ [Claude Code](https://claude.com/claude-code) (Anthropic) was used in building
133
+ this package, to support implementation, to write tests, and to write the
134
+ documentation. Individual commits record it as a co-author.
135
+
136
+ The methods, the design decisions, and the scientific claims are the authors'.
137
+ Everything committed was reviewed, and the behavioural claims in the docstrings
138
+ and the user guide are backed by the test suite or by the measurements cited
139
+ alongside them.
140
+
141
+ ## Contributing
142
+
143
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development setup, the versioning
144
+ policy, and what a pull request needs. Planned work is tracked in the
145
+ [issue tracker](https://github.com/NiklasBrunn/structboost/issues).
146
+
147
+ Licensed under the [MIT License](LICENSE).
@@ -0,0 +1,35 @@
1
+ """Reject a too-old interpreter before anything imports the package.
2
+
3
+ ``pythonpath = ["src", "."]`` in ``pyproject.toml`` makes pytest import this
4
+ checkout rather than an installed copy. That is deliberate, so that a bare
5
+ ``pytest`` tests the code in front of you. It also means nothing was installed,
6
+ so nothing enforced ``requires-python = ">=3.10"``. On an older interpreter the
7
+ first import instead dies inside ``_boosting.py`` on a runtime ``X | Y`` union,
8
+ with a ``TypeError`` that names the two operands and not the cause.
9
+
10
+ The usual way this happens is a stale ``pytest`` sitting earlier on ``PATH`` than
11
+ the project's own interpreter, so the message names that and the way out.
12
+
13
+ This file has to stay importable on the versions it rejects: no ``X | Y``
14
+ annotations, nothing newer than the floor below.
15
+ """
16
+
17
+ import sys
18
+
19
+ import pytest
20
+
21
+ #: Keep in step with ``requires-python`` in ``pyproject.toml``.
22
+ MIN_PYTHON = (3, 10)
23
+
24
+
25
+ def pytest_configure(config):
26
+ """Abort with a readable message instead of a TypeError during collection."""
27
+ if sys.version_info < MIN_PYTHON:
28
+ wanted = ".".join(str(part) for part in MIN_PYTHON)
29
+ running = ".".join(str(part) for part in sys.version_info[:3])
30
+ raise pytest.UsageError(
31
+ f"structboost needs Python >= {wanted} but this pytest runs on "
32
+ f"{running} ({sys.executable}). A stale pytest earlier on PATH than "
33
+ "the project interpreter is the usual cause: run 'python -m pytest' "
34
+ "instead, or put the project's environment first on PATH."
35
+ )