PyO3-Polars OptBinning extension with Rust-side optimization kernels
Project description
polars-optbinning
Rust-powered rule binning for Polars DataFrames and expressions.
The main API is BinningTransformer: fit optimized 1D rules for one or more
features, keep the fitted rule groups, and reuse them in Polars pipelines.
Installation
Install the published package from PyPI:
pip install polars-optbinning
For local development:
uv sync --group dev
uv run maturin develop --release
Usage
import polars as pl
from polars_optbinning import BinningTransformer, Objective, Stat
df = pl.DataFrame(
{
"x1": [1.0, 1.2, 2.0, 2.2, 3.1, 3.4, 4.5, 5.0, 5.2, 6.0],
"x2": [10.0, 9.8, 9.5, 8.9, 8.1, 7.3, 6.5, 5.9, 5.1, 4.2],
"y": [0, 0, 0, 1, 0, 1, 1, 1, 1, 1],
}
)
transformer = BinningTransformer(
features=[pl.col("x1"), pl.col("x2")],
target=pl.col("y"),
)
transformer.fit(
df,
objective=Objective.iv(),
mapping=[Stat.woe(), Stat.count()],
max_bins=4,
)
eager_out = transformer.transform(df)
pipeline_out = df.with_columns(transformer.pl_transform)
print(eager_out)
print(pipeline_out)
print(transformer.summary())
summary() can re-execute the current rules and calculate statistics that are
independent of the transform mapping:
stats = transformer.summary(
extra_stat=[Stat.count(), Stat.avg(), Stat.target_mean()]
)
Fitted transformers reuse their selected training frame. Pass
summary(extra_stat=[...], df=new_df) to calculate the statistics on another
data set. Parsed and manually constructed transformers require df; pass
target="label" as well when requesting target-based statistics. The
mapping supplied to fit() or parse() remains the explicit schema produced
by transform() and pl_transform; Rule stats not selected by mapping are not
automatically expanded into transform columns.
Pairwise 2D binning creates one feature for each feature pair:
pairwise = BinningTransformer(
features=[pl.col("x1"), pl.col("x2")],
target=pl.col("y"),
prefix="pair_",
)
pairwise.fit(
df,
objective=Objective.iv(),
mapping=[Stat.index()],
max_bins=4,
mode="pairwise_2d",
progress=True,
)
out = df.with_columns(pairwise.pl_transform)
For a continuous target regression-style example, see examples/example_linear.py.
Native XGBoost and LightGBM tree models can be parsed into reusable rule groups:
model = BinningTransformer.parse(
"examples/models/multiclass_xgb.json",
mapping=[Stat.bin_value()],
prefix="xgb_",
)
tree_outputs = df.with_columns(model.pl_transform)
raw_margin = df.with_columns(prediction=model.predict())
probability = df.with_columns(prediction=model.predict(output="probability"))
classes = df.with_columns(prediction=model.predict(output="class"))
parse() accepts XGBoost save_model() JSON/UBJ and LightGBM
Booster.save_model() text files, paths, and readable file objects. Parsed
transformers expose one editable RuleGroup per tree and cannot be fitted
again. See examples/example_multiclass_models.py for native model training,
serialization, parsing, and Polars prediction comparisons.
Manual rule engines are also supported:
from polars_optbinning import Condition, Rule, RuleGroup
group = RuleGroup(
"x",
["x"],
[
Rule(Condition.interval("x", None, 2.0), [Stat.const(0.1)]),
Rule(Condition.interval("x", 2.0, None), [Stat.const(0.9)]),
],
[Stat.const(0.0)],
)
engine = BinningTransformer(groups=[group])
out = pl.DataFrame({"x": [1.0, 2.5]}).with_columns(engine.pl_transform)
Conditions can be composed as sets across one or more features:
high_risk = (Condition.interval("age", 65.0, None) & Condition.is_in("region", [1.0, 2.0]))
eligible = high_risk | Condition.interval("score", 700.0, None)
excluded = eligible - Condition.is_in("region", [9.0])
assert excluded.features == ["age", "region", "score"]
excluded = excluded.simplify()
group = RuleGroup(
"decision",
["age", "region", "score"],
[
Rule(excluded, [Stat.const(1.0)]),
Rule(Condition.otherwise(), [Stat.const(0.0)]),
],
[Stat.const(0.0)],
)
otherwise is optional, but when present it must be the final rule. Empty groups and
unmatched rows without an otherwise rule produce null outputs. Fitted binning groups
are ordered by descending fitted count and always include a final otherwise rule.
Rules may be compiled into an explicit binary execution chain:
group.compile()
assert group.execution_kind == "rule_chain"
Perpetual GBDT
Multidimensional gradient boosting is available through the same transformer:
model = BinningTransformer(features=["x1", "x2"], target="y").fit(
df,
mode="gbdt",
objective=Objective.squared_loss(),
extra_params={
"budget": 0.5,
"iteration_limit": 100,
"categorical_features": ["x2"],
},
)
raw = df.select(model.predict())
tree_outputs = df.select(model.pl_transform)
importance = model.feature_importance()
Classification uses Objective.log_loss(). Integer class labels are preserved, including
non-contiguous labels. extra_params is validated strictly and feature-related settings
use feature names rather than column indices.
Tree rules expose Stat.gain(), defined as the cumulative split gain along the root-to-leaf
path. feature_importance() instead counts every internal split exactly once and returns
feature, gain, gain_normalized, and split_count. Structural rule edits invalidate
the original tree importance. See examples/example_perpetual.py for a Titanic survival
classification example with categorical and missing-value handling.
The Rust integration currently pins perpetual 3.0.0-rc.2, the first compatible release
line that builds on stable Rust; perpetual 2.1.0 requires a nightly-only compiler feature.
Current Scope
- Optimized multi-feature 1D binning via
BinningTransformer.fit. - Typed
ObjectiveandStatselectors. - Eager
transform(df)and expression-friendlypl_transform. summary()returning fitted rule statistics as a Polars DataFrame.- Pairwise 2D rectangular binning via
mode="pairwise_2d". - XGBoost/LightGBM numeric tree parsing, leaf-rule transforms, and model prediction.
ND optimized fitting is intentionally paused while the reusable rule engine interface settles.
Publishing
The release script verifies that pyproject.toml and Cargo.toml use the same
version, builds optimized wheel and source distributions, publishes them to
PyPI, creates the matching v<version> Gitee tag, and mirrors the files to a
Gitee Release.
Token authentication is recommended:
$env:GITEE_TOKEN = "<personal-access-token>"
$env:MATURIN_PYPI_TOKEN = "<pypi-api-token>"
uv run python scripts/gitee_release.py --no-install-project
PYPI_API_TOKEN is accepted as an alias. Account/password authentication is
also supported through either MATURIN_USERNAME + MATURIN_PASSWORD or
PYPI_USERNAME + PYPI_PASSWORD.
PyPI distributions are immutable. On a repeated run, --skip-existing leaves
already published files untouched while allowing wheels for additional
platforms to be uploaded. The Gitee Release is updated and same-named
attachments are replaced. Existing wheels for other platforms are preserved.
Set SKIP_BUILD=1 to upload files already present in WHEEL_DIR (dist by
default), SKIP_PYPI=1 for a Gitee-only mirror, or SKIP_GITEE=1 for a
PyPI-only release. All credentials must be stored in the CI service's secret
variable store.
Release checklist:
- Update the version in both
pyproject.tomlandCargo.toml. - Update
CHANGELOG.md. - Run Clippy and the Python test suite.
- Run the release script from the commit that should receive the version tag.
See CONTRIBUTING.md, SECURITY.md, and LICENSE for the project policies.
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 Distribution
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 polars_optbinning-0.2.0.tar.gz.
File metadata
- Download URL: polars_optbinning-0.2.0.tar.gz
- Upload date:
- Size: 101.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28e98fb738b297d35893c283a9a16bffd09be1abffa7eeb955b63fd060001883
|
|
| MD5 |
29ab4ae1d8377f1b148fc4b55997c22c
|
|
| BLAKE2b-256 |
52870911152a7b8700ae63dff941ec644b8798e5ed7234fc1edb5afcfb615ad2
|
File details
Details for the file polars_optbinning-0.2.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: polars_optbinning-0.2.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 5.7 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
585132a6675457edce10361182079ba6a48f1eacf8792297674fe03aca4e3fb2
|
|
| MD5 |
203531cb1b0a66fd0fe7ce8811cbeef4
|
|
| BLAKE2b-256 |
5a95c2683e986618186d3213c6b133628240dc0a6e30628bf2e83a44aac44a74
|