Symbolic regression with automatic null-baseline ΔBIC. Find formulas AND prove they're not noise.
Project description
nullbic
Symbolic regression with automatic null-baseline ΔBIC. Find formulas AND prove they're not noise.
from nullbic import Dataset, discover
ds = Dataset.from_csv("data.csv", target="y")
report = discover(ds, n_generations=40)
print(report.summary())
print("real signal?", report.is_real_signal())
Why this exists
Existing symbolic-regression tools (PySR, Eureqa, gplearn,
SymbolicRegression.jl) all give you a formula. None of them, by default,
tell you whether that formula meaningfully beats a constant model or a
trivial linear fit. Most "discoveries" published with these tools are
indistinguishable from noise, and the user has no easy way to tell.
nullbic ships those baselines as a first-class output:
- ΔBIC vs F = const (the mean predictor)
- ΔBIC vs F = linear (best OLS on all features)
- z-score vs shuffled-target distribution (30 shuffles by default)
Verdict.STRONG/WEAK/NOISEassigned automatically
If the discovered formula doesn't beat all three baselines, the verdict downgrades. No silent overfit.
Install
git clone https://github.com/yourname/nullbic
cd nullbic
pip install -e .
# optional: pandas integration
pip install -e ".[pandas]"
The Rust core ships as a pre-built shared library inside the package
(nullbic/_dll/nullbic.dll on Windows; .so / .dylib on Linux / macOS).
No Rust toolchain needed at install time.
Usage
From a pandas DataFrame
import pandas as pd
from nullbic import Dataset, discover
df = pd.read_csv("merge_results.csv")
ds = Dataset.from_pandas(df, target="cosine_sim")
rep = discover(ds, n_generations=40, pop_size=200, max_depth=4)
if rep.is_real_signal():
print("Formula:", rep.formula)
print(f"ΔBIC vs const : {rep.delta_bic_const:+.1f}")
print(f"ΔBIC vs linear: {rep.delta_bic_linear:+.1f}")
print(f"z vs shuffled : {rep.z_vs_shuffled:+.2f}")
From a CSV
from nullbic import Dataset, discover
ds = Dataset.from_csv("data.csv", target="y")
print(discover(ds).summary())
From a CLI
nullbic data.csv y --gens=40 --pop=200 --depth=4
Exit code is 0 if the verdict is STRONG or WEAK, 1 if NOISE —
handy in CI/CD signal-validation pipelines.
Three real use cases
1. Kaggle / tabular insight
Point at any cleaned dataset. Get a formula. Get the proof it's not in the noise.
2. Black-box surrogate
Approximate an XGBoost / NN model with a symbolic surrogate; the verdict tells you when the surrogate is meaningful vs cosmetic.
3. Empirical-law audit
Feed in a paper's claimed empirical relationship. The verdict says whether the relationship really beats a linear baseline on the given data.
How the verdict is assigned
| Verdict | Criteria |
|---|---|
STRONG |
ΔBIC vs const < −10 AND ΔBIC vs linear < −10 AND z vs shuffled < −2 |
WEAK |
Beats const but not all three thresholds |
NOISE |
Doesn't beat const → formula is not extracting any signal |
These thresholds match standard model-selection conventions (Kass & Raftery 1995 / Schwarz 1978).
Architecture
nullbic (Python package)
└── core.py → public API (Dataset, discover, …)
└── _bindings.py → ctypes layer (private)
└── _dll/nullbic.dll → Rust shared library (pre-built)
nullbic-core (Rust crate, ~600 LOC)
└── dataset → tabular rows + train/test split + shuffled
└── expr → expression trees over named features
└── optimizer → single-level GA, niching, hyper-mutation
└── baselines → F=const + F=linear (OLS) + shuffled-target
└── c_api → extern "C" entry points
Performance
Typical run on 500 rows × 10 features, 40 generations, pop 200:
- ~30–80 ms wall-clock on one core
- ~150 MB peak RAM
- Deterministic for a given seed
The GA is parallel via Rayon; throughput scales near-linearly with cores.
Limitations and honest caveats
- The GA is intentionally simple. PySR is more sophisticated when raw
accuracy is the only goal.
nullbictrades a few percent of accuracy for the auto-falsification report. - The "linear baseline" is plain OLS with a tiny ridge for numerical
safety; it's not feature-engineered. If you're comparing to a serious
linear model, replace
delta_bic_linearwith your own ΔBIC. - 580 rows is comfortable. Below ~50 rows, results are unreliable — the shuffled-target distribution is too noisy to anchor the z-score.
License
MIT. See LICENSE.
Citation
If nullbic contributes to a paper, please cite it as:
nullbic: symbolic regression with auto-falsification ΔBIC. 2026.
Related
- PySR — sophisticated symbolic regression
- gplearn — sklearn-compatible GP
- SymbolicRegression.jl — Julia
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nullbic-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: nullbic-0.1.0-py3-none-win_amd64.whl
- Upload date:
- Size: 671.3 kB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a436c378b3ffad3c6f7b033d1ba63e8a1545fbbb50bca57eb40312f305c3331
|
|
| MD5 |
9054fd48fa7f785c8d71707464c7bdcc
|
|
| BLAKE2b-256 |
6b7e93053e3d8231618e47a01339672a08e03c43344ce06e16301f4d053cf8e3
|