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.
- structboost-0.1.0/.gitignore +107 -0
- structboost-0.1.0/CHANGELOG.md +32 -0
- structboost-0.1.0/CITATION.cff +9 -0
- structboost-0.1.0/CODE_OF_CONDUCT.md +8 -0
- structboost-0.1.0/CONTRIBUTING.md +65 -0
- structboost-0.1.0/LICENSE +21 -0
- structboost-0.1.0/PKG-INFO +219 -0
- structboost-0.1.0/README.md +147 -0
- structboost-0.1.0/conftest.py +35 -0
- structboost-0.1.0/pyproject.toml +131 -0
- structboost-0.1.0/src/structboost/__init__.py +94 -0
- structboost-0.1.0/src/structboost/_annotation.py +266 -0
- structboost-0.1.0/src/structboost/_boosting.py +552 -0
- structboost-0.1.0/src/structboost/_decoder.py +109 -0
- structboost-0.1.0/src/structboost/_encoder.py +88 -0
- structboost-0.1.0/src/structboost/_explorer.py +842 -0
- structboost-0.1.0/src/structboost/_io.py +302 -0
- structboost-0.1.0/src/structboost/_model.py +3483 -0
- structboost-0.1.0/src/structboost/_persistence.py +326 -0
- structboost-0.1.0/src/structboost/_plotting.py +301 -0
- structboost-0.1.0/src/structboost/_simulation.py +867 -0
- structboost-0.1.0/src/structboost/_stability.py +412 -0
- structboost-0.1.0/src/structboost/_types.py +393 -0
- structboost-0.1.0/src/structboost/_utils.py +509 -0
- structboost-0.1.0/src/structboost/py.typed +0 -0
- structboost-0.1.0/tests/test_allboost.py +540 -0
- structboost-0.1.0/tests/test_annotation.py +298 -0
- structboost-0.1.0/tests/test_bae.py +1631 -0
- structboost-0.1.0/tests/test_bae_diagnostics.py +334 -0
- structboost-0.1.0/tests/test_bae_init.py +284 -0
- structboost-0.1.0/tests/test_bae_layer.py +187 -0
- structboost-0.1.0/tests/test_bae_persistence.py +338 -0
- structboost-0.1.0/tests/test_bae_transfer.py +935 -0
- structboost-0.1.0/tests/test_batch_integration.py +175 -0
- structboost-0.1.0/tests/test_explorer.py +616 -0
- structboost-0.1.0/tests/test_linear_ceiling.py +77 -0
- structboost-0.1.0/tests/test_obs_encoding.py +172 -0
- structboost-0.1.0/tests/test_public_api.py +57 -0
- structboost-0.1.0/tests/test_resolve_mandatory.py +81 -0
- structboost-0.1.0/tests/test_rng_isolation.py +160 -0
- structboost-0.1.0/tests/test_simulation.py +618 -0
- 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
|
+
)
|