structboost 0.2.0__tar.gz → 0.3.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.2.0 → structboost-0.3.0}/CHANGELOG.md +70 -0
- {structboost-0.2.0 → structboost-0.3.0}/CITATION.cff +2 -2
- {structboost-0.2.0 → structboost-0.3.0}/PKG-INFO +3 -3
- {structboost-0.2.0 → structboost-0.3.0}/README.md +1 -1
- {structboost-0.2.0 → structboost-0.3.0}/pyproject.toml +1 -1
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_decoder.py +0 -6
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_model.py +145 -460
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_persistence.py +12 -6
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_types.py +7 -39
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_utils.py +10 -12
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_bae.py +43 -105
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_bae_diagnostics.py +1 -9
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_bae_layer.py +1 -1
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_bae_persistence.py +0 -13
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_bae_transfer.py +4 -12
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_covcache_precompute.py +1 -1
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_rng_isolation.py +1 -1
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_stability.py +66 -149
- {structboost-0.2.0 → structboost-0.3.0}/.gitignore +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/CONTRIBUTING.md +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/LICENSE +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/conftest.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/__init__.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_annotation.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_boosting.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_encoder.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_explorer.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_io.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_plotting.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_simulation.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/_stability.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/src/structboost/py.typed +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_allboost.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_annotation.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_bae_init.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_batch_integration.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_explorer.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_linear_ceiling.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_obs_encoding.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_public_api.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_resolve_mandatory.py +0 -0
- {structboost-0.2.0 → structboost-0.3.0}/tests/test_simulation.py +0 -0
|
@@ -3,6 +3,76 @@
|
|
|
3
3
|
Releases follow [semantic versioning](https://semver.org). While the project is
|
|
4
4
|
pre-1.0, a minor bump may break API.
|
|
5
5
|
|
|
6
|
+
### [0.3.0] - 2026-08-11
|
|
7
|
+
|
|
8
|
+
**Breaking.** Five settings are gone and `BAE.stability_selection` has one mode
|
|
9
|
+
instead of two. Every removed option was off by default, so a fit that took the
|
|
10
|
+
defaults is unaffected: the encoder matrix is bitwise identical across a plain
|
|
11
|
+
fit, a batch-integrated fit, and both disentanglement methods. What changes is the
|
|
12
|
+
surface you have to reason about.
|
|
13
|
+
|
|
14
|
+
`boosting_nu` stays at `0.1`. Raising it to `0.3` was measured and deferred: at
|
|
15
|
+
the default `stepno=50` it matches `0.1`'s marker recovery three to four times
|
|
16
|
+
faster and wins outright on low-signal data, but then degrades if training
|
|
17
|
+
continues, and the training-MSE stopping rule cannot see it happening. The guide
|
|
18
|
+
records the numbers; the two changes belong together and will land together.
|
|
19
|
+
|
|
20
|
+
**Checkpoints written by 0.2.0 no longer load.** `restore_payload` splats the
|
|
21
|
+
stored config into `BAEConfig`, so a dropped field is an unexpected keyword
|
|
22
|
+
argument rather than a missing one. The format is bumped to 5 and the loader
|
|
23
|
+
refuses 4 by name. No migration is written: the method is under active
|
|
24
|
+
development and a checkpoint is cheap to regenerate, whereas a compatibility
|
|
25
|
+
shim for options that no longer exist is not.
|
|
26
|
+
|
|
27
|
+
Removed from `BAEConfig`:
|
|
28
|
+
|
|
29
|
+
- `decoder_dropout_rate` and `decoder_use_batch_norm`. Batch norm was already
|
|
30
|
+
documented as harmful (marker-recovery F1 0.59-0.73) because the boosting
|
|
31
|
+
target is computed with the decoder in eval mode and the update applied in
|
|
32
|
+
train mode. Dropout has exactly the same inconsistency and it was never written
|
|
33
|
+
down: the target comes from the full network, the update from a thinned one.
|
|
34
|
+
With both gone the decoder is a deterministic per-cell function, which is the
|
|
35
|
+
property `_compute_boosting_targets` has always relied on.
|
|
36
|
+
- `disentanglement_standardize`. Both `disentanglement` methods stay.
|
|
37
|
+
- `standardize_targets`. It was a genuine trade-off — higher selection precision,
|
|
38
|
+
roughly half the recall — but it is superseded by
|
|
39
|
+
`stability_selection(threshold=...)` -> `stable_encoder()` -> `apply_encoder()`,
|
|
40
|
+
which is the same trade with a dial that reports what it is doing. Two knobs for
|
|
41
|
+
one trade-off is worse than one.
|
|
42
|
+
|
|
43
|
+
Removed from `fit`:
|
|
44
|
+
|
|
45
|
+
- `balance_obs`, and with it the `sample_weights` parameter that threaded through
|
|
46
|
+
nine methods and both stability paths. Its own documentation conceded the
|
|
47
|
+
limit: the weights never reached the `allboost` fit, so gene selection stayed
|
|
48
|
+
unbalanced no matter what they were set to. The measured effect was modest (per
|
|
49
|
+
group reconstruction-MSE spread 0.231 -> 0.150) for a mechanism that promised
|
|
50
|
+
more than it delivered.
|
|
51
|
+
|
|
52
|
+
`BAE.stability_selection` is now iteration mode only; `mode`, `subsample_frac`,
|
|
53
|
+
`n_subsamples` and `n_iterations` are gone, and `fit(stability_selection=...)`
|
|
54
|
+
takes a bool. Iteration mode was already the default and measures the lower
|
|
55
|
+
false-discovery rate (0.26 against 0.31). The subsample path stays available as
|
|
56
|
+
the standalone `structboost.stability_selection`, which is where it belongs: it
|
|
57
|
+
resamples cells in the Meinshausen-Buhlmann scheme and works on an `allboost`
|
|
58
|
+
problem, so supervised users with no training loop to iterate over still have it.
|
|
59
|
+
The Meinshausen-Buhlmann bound it reports was measured to be violated by roughly
|
|
60
|
+
an order of magnitude when the targets come from a model fitted on the same
|
|
61
|
+
cells, and that warning moved with it.
|
|
62
|
+
|
|
63
|
+
`uns["bae"]["variance_explained"]` is now written by every fit. It used to appear
|
|
64
|
+
only when a covariate argument was passed, which left the quality workflow the
|
|
65
|
+
guide documents — compare it against `linear_ceiling` — raising `KeyError` on a
|
|
66
|
+
plain `fit(adata)`. The metric has nothing to do with covariates; only the
|
|
67
|
+
per-group breakdown does, and that stays behind the covariate guard.
|
|
68
|
+
|
|
69
|
+
Two fixes found along the way: the `allboost` example in the guide passed
|
|
70
|
+
`mode="standard"`, an argument `allboost` has never accepted, so it raised
|
|
71
|
+
`TypeError` as written. And `disentangle_boosting_targets` projects through the
|
|
72
|
+
origin, with no intercept — exact residualization only on centered targets. That
|
|
73
|
+
was masked by `disentanglement_standardize`, which centered them; with the flag
|
|
74
|
+
gone the assumption is documented instead.
|
|
75
|
+
|
|
6
76
|
### [0.2.0] - 2026-08-05
|
|
7
77
|
|
|
8
78
|
The boosting loop got faster without changing what it computes. Measured
|
|
@@ -2,8 +2,8 @@ cff-version: 1.2.0
|
|
|
2
2
|
message: "If you use structboost in your research, please cite it."
|
|
3
3
|
title: "structboost"
|
|
4
4
|
type: software
|
|
5
|
-
version: "0.
|
|
6
|
-
date-released: "2026-08-
|
|
5
|
+
version: "0.3.0"
|
|
6
|
+
date-released: "2026-08-11"
|
|
7
7
|
license: MIT
|
|
8
8
|
authors:
|
|
9
9
|
- family-names: Brunn
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: structboost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Boosting Autoencoders (BAE) and componentwise L2 boosting utilities for scverse-style workflows.
|
|
5
5
|
Project-URL: Homepage, https://github.com/NiklasBrunn/structboost
|
|
6
6
|
Project-URL: Documentation, https://niklasbrunn.github.io/structboost
|
|
@@ -153,7 +153,7 @@ is not identifiable: many different sparse gene sets reconstruct the data about
|
|
|
153
153
|
equally well, and a fit returns one of them.
|
|
154
154
|
|
|
155
155
|
```python
|
|
156
|
-
res = model.stability_selection(adata
|
|
156
|
+
res = model.stability_selection(adata)
|
|
157
157
|
genes = [adata.var_names[res.stable_support[:, j]] for j in range(res.frequency.shape[1])]
|
|
158
158
|
```
|
|
159
159
|
|
|
@@ -78,7 +78,7 @@ is not identifiable: many different sparse gene sets reconstruct the data about
|
|
|
78
78
|
equally well, and a fit returns one of them.
|
|
79
79
|
|
|
80
80
|
```python
|
|
81
|
-
res = model.stability_selection(adata
|
|
81
|
+
res = model.stability_selection(adata)
|
|
82
82
|
genes = [adata.var_names[res.stable_support[:, j]] for j in range(res.frequency.shape[1])]
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "structboost"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Boosting Autoencoders (BAE) and componentwise L2 boosting utilities for scverse-style workflows."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -55,11 +55,7 @@ class BAEDecoder(nn.Module):
|
|
|
55
55
|
|
|
56
56
|
for i in range(len(dims) - 1):
|
|
57
57
|
layers.append(nn.Linear(dims[i], dims[i + 1]))
|
|
58
|
-
if config.decoder_use_batch_norm:
|
|
59
|
-
layers.append(nn.BatchNorm1d(dims[i + 1]))
|
|
60
58
|
layers.append(self.activation)
|
|
61
|
-
if config.decoder_dropout_rate > 0:
|
|
62
|
-
layers.append(nn.Dropout(config.decoder_dropout_rate))
|
|
63
59
|
|
|
64
60
|
self.hidden = nn.Sequential(*layers)
|
|
65
61
|
# Final projection to output space
|
|
@@ -83,8 +79,6 @@ class BAEDecoder(nn.Module):
|
|
|
83
79
|
for module in self.modules():
|
|
84
80
|
if isinstance(module, nn.Linear):
|
|
85
81
|
module.reset_parameters()
|
|
86
|
-
elif isinstance(module, nn.BatchNorm1d):
|
|
87
|
-
module.reset_parameters()
|
|
88
82
|
|
|
89
83
|
def forward(self, z: torch.Tensor, covariates: torch.Tensor | None = None) -> torch.Tensor:
|
|
90
84
|
"""Decode latent representation to reconstruction.
|