beamfeat 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.
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ dist/
4
+ build/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ site/
8
+ .venv/
9
+ paper/
10
+ *.feather
11
+ benchmarks/independent/openfe_tmp_data.feather
@@ -0,0 +1,29 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ type: software
4
+ title: "beamfeat: beam-search feature construction with FDR-controlled selection"
5
+ version: 0.1.0
6
+ date-released: 2026-07-23
7
+ license: MIT
8
+ authors:
9
+ - family-names: "Daramola"
10
+ given-names: "Olanrewaju M."
11
+ orcid: "https://orcid.org/0009-0006-3327-2047"
12
+ repository-code: "https://github.com/LD-Shell/beamfeat"
13
+ keywords:
14
+ - feature engineering
15
+ - feature selection
16
+ - false discovery rate
17
+ - knockoffs
18
+ # After archiving the release on Zenodo, add the concept DOI:
19
+ # doi: 10.5281/zenodo.XXXXXXX
20
+ # After JOSS acceptance, add:
21
+ # preferred-citation:
22
+ # type: article
23
+ # title: "beamfeat: beam-search feature construction with false-discovery-rate controlled selection"
24
+ # authors:
25
+ # - family-names: "Daramola"
26
+ # given-names: "Olanrewaju M."
27
+ # journal: "Journal of Open Source Software"
28
+ # doi: <JOSS DOI>
29
+ # year: 2026
beamfeat-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Olanrewaju M. Daramola
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,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: beamfeat
3
+ Version: 0.1.0
4
+ Summary: Beam-search feature construction with false-discovery-rate controlled selection
5
+ Project-URL: Homepage, https://github.com/LD-Shell/beamfeat
6
+ Project-URL: Documentation, https://github.com/LD-Shell/beamfeat#readme
7
+ Project-URL: Issues, https://github.com/LD-Shell/beamfeat/issues
8
+ Author: Olanrewaju M. Daramola
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: automl,false-discovery-rate,feature-engineering,feature-selection,knockoffs,symbolic-features
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: numpy>=1.26
22
+ Requires-Dist: scikit-learn>=1.6
23
+ Provides-Extra: all
24
+ Requires-Dist: pint>=0.24; extra == 'all'
25
+ Requires-Dist: pytest>=8; extra == 'all'
26
+ Requires-Dist: sympy>=1.13; extra == 'all'
27
+ Provides-Extra: benchmarks
28
+ Requires-Dist: lightgbm>=4; extra == 'benchmarks'
29
+ Provides-Extra: display
30
+ Requires-Dist: sympy>=1.13; extra == 'display'
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocs-jupyter; extra == 'docs'
33
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
34
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
35
+ Provides-Extra: test
36
+ Requires-Dist: pytest>=8; extra == 'test'
37
+ Requires-Dist: sympy>=1.13; extra == 'test'
38
+ Provides-Extra: units
39
+ Requires-Dist: pint>=0.24; extra == 'units'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # beamfeat
43
+
44
+ [![CI](https://github.com/LD-Shell/beamfeat/actions/workflows/ci.yml/badge.svg)](https://github.com/LD-Shell/beamfeat/actions/workflows/ci.yml)
45
+ [![PyPI](https://img.shields.io/pypi/v/beamfeat)](https://pypi.org/project/beamfeat/)
46
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue)](https://github.com/LD-Shell/beamfeat)
47
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/LD-Shell/beamfeat/blob/main/LICENSE)
48
+ <!-- After the Zenodo archive, add:
49
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.XXXXXXX.svg)](https://doi.org/10.5281/zenodo.XXXXXXX)
50
+ -->
51
+
52
+ Beam-search feature construction with false-discovery-rate (FDR) controlled
53
+ selection, packaged as scikit-learn estimators.
54
+
55
+ beamfeat builds interpretable mathematical expressions from your columns —
56
+ `(x0 / x2) * x1`, `log(a) * log(b)` — searches for the ones that explain the
57
+ target, and then screens them with a selection procedure whose statistical
58
+ guarantee is stated, tested, and honestly reported when it cannot be given.
59
+
60
+ ```python
61
+ from beamfeat import BeamFeatRegressor
62
+
63
+ model = BeamFeatRegressor(max_depth=2, beam_width=30).fit(X, y)
64
+
65
+ model.formulas() # ['((x0 / x2) * x1)', ...]
66
+ model.equation() # 'y = 0.9847*((x0 / x2) * x1) + ...'
67
+ model.fdr_controlled_ # True: the features carry the stated FDR guarantee
68
+ model.predict(X_new)
69
+ ```
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ pip install beamfeat # core: numpy + scikit-learn only
75
+ # before the first PyPI release, install from source:
76
+ # pip install git+https://github.com/LD-Shell/beamfeat
77
+ pip install "beamfeat[units]" # + pint, for unit-aware search (units as
78
+ # strings like "kg" or as pint quantities)
79
+ pip install "beamfeat[all]" # + sympy display support and test deps
80
+ ```
81
+
82
+ Python ≥ 3.10. There are **no upper version pins**: the test suite runs
83
+ against the oldest supported stack (numpy 1.26 / scikit-learn 1.6) and
84
+ through the current releases (verified up to scikit-learn 1.9 / numpy 2.5).
85
+
86
+ ## What it does
87
+
88
+ 1. **Search.** A beam search over an expression DAG proposes and scores
89
+ compositions of unary (`log`, `sqrt`, `reciprocal`, `square`, `abs`) and
90
+ binary (`*`, `/`, `+`, `-`) operators, with structural deduplication,
91
+ redundancy pruning, and cost O(depth × beam²) rather than the exhaustive
92
+ O(operators^depth).
93
+ 2. **Selection.** Candidates are tested for association with the target under
94
+ procedures that control the false discovery rate (details below), on a
95
+ held-out split of the training rows that the search never saw.
96
+ 3. **Fit.** A parsimony step (greedy forward selection within the screened
97
+ set, on by default) keeps the compact subset the equation uses — the full
98
+ screened set with per-candidate p- and q-values stays auditable in
99
+ `selection_report_` — then a linear model fits it, readable via
100
+ `.equation()`.
101
+
102
+ Numerical failures (overflow, domain errors) are recorded and excluded, never
103
+ silently masked. Optional unit propagation — units given as pint quantities
104
+ or as plain strings like `"kg"` or `"m / s**2"` — rejects dimensionally
105
+ invalid expressions at construction time — on a mechanics example this pruned 62% of
106
+ proposals before any data work.
107
+
108
+ ## The guarantees, stated precisely
109
+
110
+ - **Permutation selector (default).** Tests marginal association with an
111
+ exact permutation test: the statistic (|Pearson r| for regression,
112
+ eta-squared for classification) is a fixed function of the data, and
113
+ p-values use the add-one estimator of Phipson & Smyth (2010). Multiplicity
114
+ is corrected with Benjamini–Yekutieli by default (valid under arbitrary
115
+ dependence); Benjamini–Hochberg is available and is valid under positive
116
+ dependence (PRDS).
117
+ - **Knockoff selector.** Fixed-X knockoffs (Barber & Candès, 2015) when
118
+ `n ≥ 2p`: no distributional assumption on the features — engineered columns
119
+ are fine — with the guarantee requiring Gaussian noise in the linear model.
120
+ Model-X Gaussian knockoffs (Candès et al., 2018) only as an `n < 2p`
121
+ fallback, with a warning, since engineered features violate its Gaussianity
122
+ assumption.
123
+ - **Selective inference.** Search retains candidates *because* they correlate
124
+ with the target in its sample, so testing them on the same rows would bias
125
+ p-values optimistically. By default the estimators search on one half of
126
+ the training rows and select on the other (`selection_holdout=0.5`).
127
+ - **Association ≠ generalisation.** A post-fit check on the selection
128
+ holdout raises a visible `DegenerateFitWarning` if the assembled model
129
+ fails to generalise (negative held-out R², or accuracy below the majority
130
+ class) — FDR certifies the features' association, and this check covers
131
+ the separate claim that the equation built on them is sound.
132
+ - **Honest failure.** If nothing passes selection, the default
133
+ (`on_no_discoveries="empty"`) keeps no constructed features: the model
134
+ degrades to an intercept-only fit and raises a visible
135
+ `NoDiscoveriesWarning`. `"fallback"` (keep the unfiltered search output,
136
+ flagged `fdr_controlled_ = False`) and `"raise"` are available. It never
137
+ silently claims a guarantee it does not have.
138
+
139
+ Three readings of these guarantees matter. FDR is an **expectation over
140
+ repetitions**, not a per-run bound. The marginal null counts a near-duplicate
141
+ of a real signal as a **true** discovery — de-duplication is the redundancy
142
+ threshold's job, not the error-control procedure's. And every number below
143
+ carries Monte Carlo error from finite trials.
144
+
145
+ ## Measured, not assumed
146
+
147
+ All of the following are measured by the test suite or the committed
148
+ benchmark scripts, not asserted:
149
+
150
+ - Selector calibration (Gaussian designs, 25 trials): realised FDR
151
+ 0.065/0.120/0.210 at nominal 0.05/0.10/0.20 for BH, 0.020/0.033/0.065 for
152
+ BY, power 1.00 throughout. Fixed-X knockoff+ realised 0.201 at nominal 0.2.
153
+ - End-to-end pipeline (search + holdout selection) at nominal FDR 0.10:
154
+ empirical FDR 0.0000 with power 1.000 over 200 replicates, zero fallbacks;
155
+ zero selections over 60 global-null replicates.
156
+ - Feynman-equation panel (12 physics laws, 0.1% noise): 9–10/12 solved (a borderline surrogate flips across numeric stacks) at the
157
+ SRBench criterion and a stable 8/12 in exact symbolic form, ~0.9 s per
158
+ equation; the misses are named boundaries (a depth-3 rational, a literal
159
+ constant, depth-4 nesting, the Gaussian's exponential).
160
+ - Pure-noise stress: BH breached in 3/25 trials (mean FDP 0.12 vs nominal
161
+ 0.10, the PRDS caveat materialising on correlated candidates); BY in 1/25
162
+ (mean FDP 0.04). This measurement is why BY is the estimator default.
163
+ - Stress suite (signal among ten distractor columns): beamfeat's
164
+ false-feature rate — returned formulas touching only irrelevant columns —
165
+ is 0.000 at noise levels from 5% to 50% of the signal scale and at n as
166
+ small as 120, with recovery and the guarantee intact throughout; autofeat
167
+ recovers the formula equally often but returns distractor-only features at
168
+ a mean rate of 0.29 (worst 0.75 at 50% noise).
169
+ - Real data (six datasets, numeric columns): best on mpg (0.871 vs LightGBM
170
+ 0.853) and tips — where gradient boosting overfits below ridge while the
171
+ FDR gate holds beamfeat at the ceiling — tied with ridge on diamonds from
172
+ one interpretable feature, behind LightGBM on penguins and breast cancer,
173
+ and behind ridge on diabetes (0.376; autofeat 0.303, OpenFE 0.235): no
174
+ nonlinear structure exists there to find, and beamfeat does not invent
175
+ any.
176
+ - OpenFE on the same stress suite (its features + LightGBM, after shimming a
177
+ removed sklearn parameter): false-feature rate also 0.000, but 0/5 formulas
178
+ recovered and lower R² throughout — it optimises tree-model lift, not
179
+ equations.
180
+ - Heavy-tailed noise (Student t(2), infinite variance): exact formula
181
+ recovered at R² 0.956 vs LightGBM 0.866 — the permutation test is
182
+ distribution-free, so heavy tails cost it nothing.
183
+ - Known boundaries, measured: on piecewise targets trees win outright
184
+ (LightGBM 0.998 vs 0.808 — if a tree model beats beamfeat by a wide
185
+ margin, the signal is likely piecewise, not algebraic), and on Friedman #1
186
+ the 0.744-vs-0.964-oracle gap splits into a measured 0.875
187
+ screening-admissible ceiling (the marginally-quiet quadratic is priced by
188
+ the guarantee itself) and a search shortfall below it that is insensitive
189
+ to beam width and diversity settings — stated rather than hidden.
190
+ - Test suite: 370 tests at 95% statement coverage (a 90% floor is enforced
191
+ in CI), including scikit-learn's full estimator-conformance checks.
192
+ - Selection-only baseline (knockpy on raw columns, modified-FDR offset —
193
+ the only satisfiable configuration at these dimensionalities): 0/15
194
+ formulas recovered, core-suite R² at the ridge level (0.845), and a 0.11
195
+ mean false-feature rate on the stress suite — the construction step and
196
+ the holdout are the delta.
197
+ - Independent 315-fit study across nine datasets and seven methods
198
+ (`benchmarks/independent/`): mean R² 0.803 against random forest 0.810,
199
+ LightGBM 0.798, autofeat 0.751, OpenFE 0.736, featuretools −2.478 — and a
200
+ worst single fit of 0.355 where autofeat reached −2.28 and featuretools
201
+ −57.3, at ~48× autofeat's speed, with the FDR flag delivered on 45/45
202
+ fits.
203
+ - Benchmarks (10 synthetic problems with known formulas, 70/30 splits):
204
+ beamfeat mean R² 0.9993, ~0.4 s fits, 9/9 formulas recovered; autofeat
205
+ 0.9974, ~9 s, 8/9; LightGBM 0.9798; ridge 0.8442. On the purely linear
206
+ problem — where feature construction should not win — ridge ties beamfeat.
207
+
208
+ ## Relationship to autofeat
209
+
210
+ beamfeat's design began from a code review of
211
+ [autofeat](https://github.com/cod3licious/autofeat) (Horn et al., 2019) and
212
+ keeps its best idea — compact symbolic features feeding a linear model —
213
+ while replacing exhaustive expansion with beam search, heuristic
214
+ noise-threshold selection with FDR-controlled procedures, and upper-pinned
215
+ dependencies with tested floors. The generate-then-FDR-filter design has a
216
+ direct precedent in [tsfresh](https://github.com/blue-yonder/tsfresh)
217
+ (Christ et al., 2018), which filters mass-generated time-series features
218
+ under Benjamini–Yekutieli control; beamfeat applies that design to symbolic
219
+ expressions on tabular columns.
220
+
221
+ ## Independent comparison
222
+
223
+ `benchmarks/independent/` holds a 315-fit study (nine datasets, seven
224
+ methods, five splits, average ranks plus a Friedman test) with its harness,
225
+ pinned environment, data, and a notebook that reproduces the analysis. Mean
226
+ held-out R²: beamfeat 0.803, random forest 0.810, LightGBM 0.798, autofeat
227
+ 0.751, OpenFE 0.736, ridge 0.704, featuretools −2.478 — and worst single fit
228
+ of 45: beamfeat 0.355, autofeat −2.28, featuretools −57.3, with the FDR
229
+ guarantee delivered on 45/45. Read `PROVENANCE.md` first: it records what is
230
+ reproducible (beamfeat, bit-identically) and what is not (autofeat seeds
231
+ nothing internally and swung from +0.955 to −77.86 on one identical split).
232
+
233
+ ## Documentation
234
+
235
+ Tutorial notebooks live in `notebooks/` (getting started; search and scoring;
236
+ selection and units). The statistical guarantees are documented in detail in
237
+ `docs/guarantees.md` and in the module docstrings of `beamfeat.selection`. Users coming
238
+ from autofeat will find a full API mapping and version-compatibility notes
239
+ in `docs/migrating-from-autofeat.md`.
240
+
241
+ ## Citing
242
+
243
+ See `CITATION.cff`. If you use the selection procedures, please also cite the
244
+ underlying statistics: Benjamini & Hochberg (1995), Benjamini & Yekutieli
245
+ (2001), Barber & Candès (2015), Candès et al. (2018), and Phipson & Smyth
246
+ (2010).
247
+
248
+ ## License
249
+
250
+ MIT.
@@ -0,0 +1,209 @@
1
+ # beamfeat
2
+
3
+ [![CI](https://github.com/LD-Shell/beamfeat/actions/workflows/ci.yml/badge.svg)](https://github.com/LD-Shell/beamfeat/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/beamfeat)](https://pypi.org/project/beamfeat/)
5
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue)](https://github.com/LD-Shell/beamfeat)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/LD-Shell/beamfeat/blob/main/LICENSE)
7
+ <!-- After the Zenodo archive, add:
8
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.XXXXXXX.svg)](https://doi.org/10.5281/zenodo.XXXXXXX)
9
+ -->
10
+
11
+ Beam-search feature construction with false-discovery-rate (FDR) controlled
12
+ selection, packaged as scikit-learn estimators.
13
+
14
+ beamfeat builds interpretable mathematical expressions from your columns —
15
+ `(x0 / x2) * x1`, `log(a) * log(b)` — searches for the ones that explain the
16
+ target, and then screens them with a selection procedure whose statistical
17
+ guarantee is stated, tested, and honestly reported when it cannot be given.
18
+
19
+ ```python
20
+ from beamfeat import BeamFeatRegressor
21
+
22
+ model = BeamFeatRegressor(max_depth=2, beam_width=30).fit(X, y)
23
+
24
+ model.formulas() # ['((x0 / x2) * x1)', ...]
25
+ model.equation() # 'y = 0.9847*((x0 / x2) * x1) + ...'
26
+ model.fdr_controlled_ # True: the features carry the stated FDR guarantee
27
+ model.predict(X_new)
28
+ ```
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install beamfeat # core: numpy + scikit-learn only
34
+ # before the first PyPI release, install from source:
35
+ # pip install git+https://github.com/LD-Shell/beamfeat
36
+ pip install "beamfeat[units]" # + pint, for unit-aware search (units as
37
+ # strings like "kg" or as pint quantities)
38
+ pip install "beamfeat[all]" # + sympy display support and test deps
39
+ ```
40
+
41
+ Python ≥ 3.10. There are **no upper version pins**: the test suite runs
42
+ against the oldest supported stack (numpy 1.26 / scikit-learn 1.6) and
43
+ through the current releases (verified up to scikit-learn 1.9 / numpy 2.5).
44
+
45
+ ## What it does
46
+
47
+ 1. **Search.** A beam search over an expression DAG proposes and scores
48
+ compositions of unary (`log`, `sqrt`, `reciprocal`, `square`, `abs`) and
49
+ binary (`*`, `/`, `+`, `-`) operators, with structural deduplication,
50
+ redundancy pruning, and cost O(depth × beam²) rather than the exhaustive
51
+ O(operators^depth).
52
+ 2. **Selection.** Candidates are tested for association with the target under
53
+ procedures that control the false discovery rate (details below), on a
54
+ held-out split of the training rows that the search never saw.
55
+ 3. **Fit.** A parsimony step (greedy forward selection within the screened
56
+ set, on by default) keeps the compact subset the equation uses — the full
57
+ screened set with per-candidate p- and q-values stays auditable in
58
+ `selection_report_` — then a linear model fits it, readable via
59
+ `.equation()`.
60
+
61
+ Numerical failures (overflow, domain errors) are recorded and excluded, never
62
+ silently masked. Optional unit propagation — units given as pint quantities
63
+ or as plain strings like `"kg"` or `"m / s**2"` — rejects dimensionally
64
+ invalid expressions at construction time — on a mechanics example this pruned 62% of
65
+ proposals before any data work.
66
+
67
+ ## The guarantees, stated precisely
68
+
69
+ - **Permutation selector (default).** Tests marginal association with an
70
+ exact permutation test: the statistic (|Pearson r| for regression,
71
+ eta-squared for classification) is a fixed function of the data, and
72
+ p-values use the add-one estimator of Phipson & Smyth (2010). Multiplicity
73
+ is corrected with Benjamini–Yekutieli by default (valid under arbitrary
74
+ dependence); Benjamini–Hochberg is available and is valid under positive
75
+ dependence (PRDS).
76
+ - **Knockoff selector.** Fixed-X knockoffs (Barber & Candès, 2015) when
77
+ `n ≥ 2p`: no distributional assumption on the features — engineered columns
78
+ are fine — with the guarantee requiring Gaussian noise in the linear model.
79
+ Model-X Gaussian knockoffs (Candès et al., 2018) only as an `n < 2p`
80
+ fallback, with a warning, since engineered features violate its Gaussianity
81
+ assumption.
82
+ - **Selective inference.** Search retains candidates *because* they correlate
83
+ with the target in its sample, so testing them on the same rows would bias
84
+ p-values optimistically. By default the estimators search on one half of
85
+ the training rows and select on the other (`selection_holdout=0.5`).
86
+ - **Association ≠ generalisation.** A post-fit check on the selection
87
+ holdout raises a visible `DegenerateFitWarning` if the assembled model
88
+ fails to generalise (negative held-out R², or accuracy below the majority
89
+ class) — FDR certifies the features' association, and this check covers
90
+ the separate claim that the equation built on them is sound.
91
+ - **Honest failure.** If nothing passes selection, the default
92
+ (`on_no_discoveries="empty"`) keeps no constructed features: the model
93
+ degrades to an intercept-only fit and raises a visible
94
+ `NoDiscoveriesWarning`. `"fallback"` (keep the unfiltered search output,
95
+ flagged `fdr_controlled_ = False`) and `"raise"` are available. It never
96
+ silently claims a guarantee it does not have.
97
+
98
+ Three readings of these guarantees matter. FDR is an **expectation over
99
+ repetitions**, not a per-run bound. The marginal null counts a near-duplicate
100
+ of a real signal as a **true** discovery — de-duplication is the redundancy
101
+ threshold's job, not the error-control procedure's. And every number below
102
+ carries Monte Carlo error from finite trials.
103
+
104
+ ## Measured, not assumed
105
+
106
+ All of the following are measured by the test suite or the committed
107
+ benchmark scripts, not asserted:
108
+
109
+ - Selector calibration (Gaussian designs, 25 trials): realised FDR
110
+ 0.065/0.120/0.210 at nominal 0.05/0.10/0.20 for BH, 0.020/0.033/0.065 for
111
+ BY, power 1.00 throughout. Fixed-X knockoff+ realised 0.201 at nominal 0.2.
112
+ - End-to-end pipeline (search + holdout selection) at nominal FDR 0.10:
113
+ empirical FDR 0.0000 with power 1.000 over 200 replicates, zero fallbacks;
114
+ zero selections over 60 global-null replicates.
115
+ - Feynman-equation panel (12 physics laws, 0.1% noise): 9–10/12 solved (a borderline surrogate flips across numeric stacks) at the
116
+ SRBench criterion and a stable 8/12 in exact symbolic form, ~0.9 s per
117
+ equation; the misses are named boundaries (a depth-3 rational, a literal
118
+ constant, depth-4 nesting, the Gaussian's exponential).
119
+ - Pure-noise stress: BH breached in 3/25 trials (mean FDP 0.12 vs nominal
120
+ 0.10, the PRDS caveat materialising on correlated candidates); BY in 1/25
121
+ (mean FDP 0.04). This measurement is why BY is the estimator default.
122
+ - Stress suite (signal among ten distractor columns): beamfeat's
123
+ false-feature rate — returned formulas touching only irrelevant columns —
124
+ is 0.000 at noise levels from 5% to 50% of the signal scale and at n as
125
+ small as 120, with recovery and the guarantee intact throughout; autofeat
126
+ recovers the formula equally often but returns distractor-only features at
127
+ a mean rate of 0.29 (worst 0.75 at 50% noise).
128
+ - Real data (six datasets, numeric columns): best on mpg (0.871 vs LightGBM
129
+ 0.853) and tips — where gradient boosting overfits below ridge while the
130
+ FDR gate holds beamfeat at the ceiling — tied with ridge on diamonds from
131
+ one interpretable feature, behind LightGBM on penguins and breast cancer,
132
+ and behind ridge on diabetes (0.376; autofeat 0.303, OpenFE 0.235): no
133
+ nonlinear structure exists there to find, and beamfeat does not invent
134
+ any.
135
+ - OpenFE on the same stress suite (its features + LightGBM, after shimming a
136
+ removed sklearn parameter): false-feature rate also 0.000, but 0/5 formulas
137
+ recovered and lower R² throughout — it optimises tree-model lift, not
138
+ equations.
139
+ - Heavy-tailed noise (Student t(2), infinite variance): exact formula
140
+ recovered at R² 0.956 vs LightGBM 0.866 — the permutation test is
141
+ distribution-free, so heavy tails cost it nothing.
142
+ - Known boundaries, measured: on piecewise targets trees win outright
143
+ (LightGBM 0.998 vs 0.808 — if a tree model beats beamfeat by a wide
144
+ margin, the signal is likely piecewise, not algebraic), and on Friedman #1
145
+ the 0.744-vs-0.964-oracle gap splits into a measured 0.875
146
+ screening-admissible ceiling (the marginally-quiet quadratic is priced by
147
+ the guarantee itself) and a search shortfall below it that is insensitive
148
+ to beam width and diversity settings — stated rather than hidden.
149
+ - Test suite: 370 tests at 95% statement coverage (a 90% floor is enforced
150
+ in CI), including scikit-learn's full estimator-conformance checks.
151
+ - Selection-only baseline (knockpy on raw columns, modified-FDR offset —
152
+ the only satisfiable configuration at these dimensionalities): 0/15
153
+ formulas recovered, core-suite R² at the ridge level (0.845), and a 0.11
154
+ mean false-feature rate on the stress suite — the construction step and
155
+ the holdout are the delta.
156
+ - Independent 315-fit study across nine datasets and seven methods
157
+ (`benchmarks/independent/`): mean R² 0.803 against random forest 0.810,
158
+ LightGBM 0.798, autofeat 0.751, OpenFE 0.736, featuretools −2.478 — and a
159
+ worst single fit of 0.355 where autofeat reached −2.28 and featuretools
160
+ −57.3, at ~48× autofeat's speed, with the FDR flag delivered on 45/45
161
+ fits.
162
+ - Benchmarks (10 synthetic problems with known formulas, 70/30 splits):
163
+ beamfeat mean R² 0.9993, ~0.4 s fits, 9/9 formulas recovered; autofeat
164
+ 0.9974, ~9 s, 8/9; LightGBM 0.9798; ridge 0.8442. On the purely linear
165
+ problem — where feature construction should not win — ridge ties beamfeat.
166
+
167
+ ## Relationship to autofeat
168
+
169
+ beamfeat's design began from a code review of
170
+ [autofeat](https://github.com/cod3licious/autofeat) (Horn et al., 2019) and
171
+ keeps its best idea — compact symbolic features feeding a linear model —
172
+ while replacing exhaustive expansion with beam search, heuristic
173
+ noise-threshold selection with FDR-controlled procedures, and upper-pinned
174
+ dependencies with tested floors. The generate-then-FDR-filter design has a
175
+ direct precedent in [tsfresh](https://github.com/blue-yonder/tsfresh)
176
+ (Christ et al., 2018), which filters mass-generated time-series features
177
+ under Benjamini–Yekutieli control; beamfeat applies that design to symbolic
178
+ expressions on tabular columns.
179
+
180
+ ## Independent comparison
181
+
182
+ `benchmarks/independent/` holds a 315-fit study (nine datasets, seven
183
+ methods, five splits, average ranks plus a Friedman test) with its harness,
184
+ pinned environment, data, and a notebook that reproduces the analysis. Mean
185
+ held-out R²: beamfeat 0.803, random forest 0.810, LightGBM 0.798, autofeat
186
+ 0.751, OpenFE 0.736, ridge 0.704, featuretools −2.478 — and worst single fit
187
+ of 45: beamfeat 0.355, autofeat −2.28, featuretools −57.3, with the FDR
188
+ guarantee delivered on 45/45. Read `PROVENANCE.md` first: it records what is
189
+ reproducible (beamfeat, bit-identically) and what is not (autofeat seeds
190
+ nothing internally and swung from +0.955 to −77.86 on one identical split).
191
+
192
+ ## Documentation
193
+
194
+ Tutorial notebooks live in `notebooks/` (getting started; search and scoring;
195
+ selection and units). The statistical guarantees are documented in detail in
196
+ `docs/guarantees.md` and in the module docstrings of `beamfeat.selection`. Users coming
197
+ from autofeat will find a full API mapping and version-compatibility notes
198
+ in `docs/migrating-from-autofeat.md`.
199
+
200
+ ## Citing
201
+
202
+ See `CITATION.cff`. If you use the selection procedures, please also cite the
203
+ underlying statistics: Benjamini & Hochberg (1995), Benjamini & Yekutieli
204
+ (2001), Barber & Candès (2015), Candès et al. (2018), and Phipson & Smyth
205
+ (2010).
206
+
207
+ ## License
208
+
209
+ MIT.
@@ -0,0 +1,24 @@
1
+ # Benchmark artifacts
2
+
3
+ Every figure in the paper and README traces to a committed script and an
4
+ archived result file. The map:
5
+
6
+ | artifact | produced by | supports |
7
+ |---|---|---|
8
+ | `results.{json,csv}` | `run_benchmarks.py --suite core --synthetic-only --methods ridge lightgbm beamfeat` | core-suite recovery 9/9, mean R² 0.9989, mean 1.7 features |
9
+ | `results_all.{json,csv}` | `run_benchmarks.py --suite all --methods ridge lightgbm beamfeat` | Friedman #1 (0.744), heavy-tail t(2) (0.956), threshold/piecewise (0.808) |
10
+ | `results_robustness.{json,csv}` | `run_benchmarks.py --suite robustness --synthetic-only --methods beamfeat openfe` | stress-suite false-feature rates; OpenFE 0/5 recovery |
11
+ | `results_real.{json,csv}` | `run_benchmarks.py --real-only --methods ridge lightgbm beamfeat` | six-real-dataset panel (mpg, tips, diamonds, penguins, diabetes, breast cancer) |
12
+ | `results_autofeat_venv.json` | `run_benchmarks.py --suite core --methods autofeat` in the pinned environment described in `independent/requirements.txt` | autofeat core-suite comparison (0.9974, ~9 s, 8/9) |
13
+ | `results_autofeat_robustness.json` | as above, `--suite robustness` | autofeat stress false-feature rate (mean 0.29, worst 0.75) |
14
+ | `results_knockpy.{json,csv}` | `run_benchmarks.py --suite all --synthetic-only --methods knockpy` | selection-only baseline: 0/15 recovery, stress false-feature rate 0.11 |
15
+ | `results_knockpy_real.{json,csv}` | `run_benchmarks.py --real-only --methods knockpy` | selection-only baseline on the real panel |
16
+ | `feynman_results.json` | `feynman_panel.py` | physics panel: 9/12 solved, 8/12 exact form |
17
+ | `calibration_study.py` | `python benchmarks/calibration_study.py` | 200-replicate FDR 0.0000 / power 1.000; 60-null zero selections |
18
+ | `friedman_decomposition.py` | `python benchmarks/friedman_decomposition.py` | the 0.964 oracle / 0.875 admissible-ceiling decomposition |
19
+ | `reproduce_all.ipynb` | run top to bottom | orchestrates the tests, both studies, the harness, and the panel in one traceable session |
20
+ | `independent/` | see `independent/README.md` | the 315-fit external comparison study |
21
+
22
+ `beamfeat` rows regenerate bit-identically from a fresh environment;
23
+ competitor rows require the isolated environments the files above name (see
24
+ the dependency notes in `independent/PROVENANCE.md`).
@@ -0,0 +1,58 @@
1
+ # Independent comparison study
2
+
3
+ A 315-fit comparison of `beamfeat` against `autofeat`, `OpenFE`,
4
+ `featuretools`, and raw-feature baselines (ridge, random forest, LightGBM)
5
+ across nine datasets and five seeds, with average-rank and Friedman-test
6
+ analysis.
7
+
8
+ | file | contents |
9
+ |---|---|
10
+ | `REPORT.md` | the full write-up: protocol, results, statistics, conclusions |
11
+ | `PROVENANCE.md` | what is reproducible and what is not — read before citing |
12
+ | `beamfeat_benchmark.ipynb` | the study as a runnable notebook (`FULL_RUN = False` analyses the archived results in minutes; `True` regenerates them) |
13
+ | `bench.py`, `aggregate.py`, `run_all.sh` | the standalone harness |
14
+ | `requirements.txt`, `patch_openfe.py` | pinned environment and the OpenFE source patch it requires |
15
+ | `data/`, `results_as_reported/` | inputs and archived per-fit results |
16
+
17
+ ## Headline figures
18
+
19
+ Mean out-of-sample R² across the nine datasets, and the worst of the 45
20
+ individual fits per method:
21
+
22
+ | method | mean R² | worst fit | mean seconds | mean new features |
23
+ |---|---|---|---|---|
24
+ | random forest (raw) | 0.810 | 0.247 | 0.86 | — |
25
+ | **beamfeat** | **0.803** | **0.355** | **0.61** | 9.2 |
26
+ | LightGBM (raw) | 0.798 | 0.117 | 0.07 | — |
27
+ | autofeat | 0.751 | −2.282 | 29.30 | 17.1 |
28
+ | OpenFE + LightGBM | 0.736 | 0.360 | 4.40 | 7.3 |
29
+ | ridge (raw) | 0.704 | 0.353 | 0.003 | — |
30
+ | featuretools | −2.478 | −57.320 | 0.11 | 104.7 |
31
+
32
+ `beamfeat` reported `fdr_controlled_ = True` on 45/45 fits, recovered the
33
+ generating formula on every ground-truth problem, and produced no fit below
34
+ R² 0.355. Its Friedman #1 result here, 0.745, independently reproduces the
35
+ 0.744 measured by this repository's own harness.
36
+
37
+ ## Caveats that travel with these numbers
38
+
39
+ Both are documented in full in `PROVENANCE.md`; in brief:
40
+
41
+ 1. **One value was inserted by hand.** The `autofeat` Friedman #1 seed-2
42
+ result came from an interrupted run. It affects `autofeat`'s Friedman #1
43
+ mean and slightly affects the omnibus test; it touches no `beamfeat`
44
+ figure. Because `autofeat` does not seed its internal subsampling, a
45
+ re-run yields a different draw rather than the same value.
46
+ 2. **Dataset-overlap precision.** The report's comparability note is
47
+ slightly imprecise against autofeat's Table 1: diabetes also overlaps
48
+ (four of this study's four real datasets appear there), and the wine
49
+ dataset here is the red-only variant (1598×11) where autofeat evaluated
50
+ red and white combined (6497×12). Boston housing is included for that
51
+ comparability despite its removal from scikit-learn over documented
52
+ ethical concerns; the repository's own six-dataset real panel contains
53
+ no contested data.
54
+ 3. **The protocol favours linear consumers.** Every construction tool feeds
55
+ the same ridge model, which understates `OpenFE` in its intended
56
+ gradient-boosted setting and `featuretools` outside its relational use
57
+ case. `beamfeat` does not win real-tabular prediction against tuned tree
58
+ models on individual datasets (Concrete: 0.845 against LightGBM's 0.932).