Skip to main content

PyO3-Polars OptBinning extension with Rust-side optimization kernels

Project description

polars-optbinning

PyPI

基于 Rust 的 Polars DataFrame 规则最优分箱引擎。注意项目仍处于早期开发阶段,每个版本API可能会发生重大破坏性变更。

核心 API 是 BinningTransformer:为一个或多个特征拟合经优化的分箱规则,保留拟合后的规则组,并在 Polars 管道中复用。

安装

从 PyPI 安装已发布的包:

pip install polars-optbinning

本地开发:

uv sync --group dev
uv run maturin develop --release

用法

import polars as pl
from polars_optbinning import BinningTransformer, Metric, 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.transform_())
count_out = transformer.transform(df, extra_mapping=[Stat.count()])
print(eager_out)
print(pipeline_out)
print(count_out)
print(transformer.summary())

summary() 可以重新执行当前规则并计算与 transform 映射无关的统计量:

stats = transformer.summary(
    extra_stat=[
        Stat.count(),
        Stat.feat_avg(),
        Stat.target_avg(),
        Stat.avg("x2"),
        Stat.sum("x2"),
        Stat.entropy("category"),
        Stat.woe(),
    ]
)

feat_avg()feat_min()feat_max() 始终返回列表,覆盖每个规则组所用的特征。avg(expr)min(expr)max(expr)sum(expr)entropy(expr) 返回单个 Polars 表达式的标量统计量,其中 entropy(expr) 用 base-2 香浓熵统计规则内固定多分类特征的类别分布。基于表达式的输出名称从表达式派生,例如 Stat.avg("x2") 产生 x2_avgsum()woe()iv()event_rate() 默认使用初始化时指定的目标,也接受显式表达式。

拟合后的转换器会复用其选定的训练数据。传递 summary(extra_stat=[...], df=new_df) 可以在另一数据集上计算统计量。通过解析或手动构造的转换器需要提供 df,并可以使用显式表达式统计量(如 Stat.woe("label"));依赖默认目标的统计量需要 BinningTransformer(target=...)fit()parse() 中提供的 mapping 仍然是 transform()transform_() 产生的显式模式;未被 mapping 选中的规则统计量不会自动展开到 transform 列中。transform(df, extra_mapping=[...])transform_(extra_mapping=[...]) 可以在本次输出中临时追加规则统计映射,缺失的统计量会像 summary(extra_stat=...) 一样先按当前数据重新聚合,且不修改转换器自身。表达式版 transform_() 没有显式 df 参数,因此会使用 fit() 时保存的训练视图;解析或手动构造的转换器仍需要这些统计量已存在于规则中。

summary(..., transition_count=True) 会按输入 DataFrame 的当前行顺序额外统计每个规则组的转入数量:当前时刻分箱为 i 且上一时刻分箱不为 i 的次数。转移率可用 transition_count / count 在 Polars 中直接计算。

0.4.0 起,Rust core 内部的统计描述使用 typed StatKind + StatPayload 结构,Stat.to_json()Rule.to_json()RuleGroup.to_json() 中的统计字段也改为 tagged JSON;旧版 kind/value/operation 统计 JSON 不再兼容。Python 侧仍保留 Stat.count()Stat.avg("x")Stat.entropy("category") 等字符串友好的构造接口。

bins_compare(df_train, df_test, metrics=[Metric.psi()]) 可按每个分箱或树叶对两组数据分布计算 PSI、KL 散度和 Lift Drift 等指标贡献; 结果会为每个指标生成独立列,例如 psikltarget_lift_drift

comparison = transformer.bins_compare(df_train, df_test, metrics=[Metric.psi()])

group_summary(extra_stat=[...]) 可以在组粒度汇总可加性统计量,当前支持 Stat.count()Stat.iv()Stat.gain()Stat.sum(expr)

两两 2D 分箱为每个特征对创建一个特征:

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.transform_())

连续目标回归示例见 examples/example_linear.py

原生 XGBoost 和 LightGBM 树模型可以解析为可复用的规则组:

model = BinningTransformer.parse(
    "examples/models/multiclass_xgb.json",
    mapping=[Stat.bin_value()],
    prefix="xgb_",
)

tree_outputs = df.with_columns(model.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"))
eager_prediction = model.predict(df, output="class")
print(model.format_tree(0))
print(model.node_summary(0, df=df))

parse() 接受 XGBoost save_model() JSON/UBJ 文件和 LightGBM Booster.save_model() 文本文件、路径及可读文件对象。解析后的转换器每棵树暴露一个可编辑的 RuleGroup,不可再次拟合。原生模型训练、序列化、解析以及与 Polars 预测对比的完整示例见 examples/example_multiclass_models.py

也支持手动构建规则引擎:

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.transform_())

条件可以跨一个或多个特征进行集合组合:

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 是可选的,但存在时必须作为最后一条规则。空组以及没有 otherwise 规则的未匹配行输出 null。拟合后的分箱组按拟合计数的降序排列,且始终包含一条最终的 otherwise 规则。

规则可以编译为显式的二进制执行链:

group.compile()
assert group.execution_kind == "rule_chain"

原生 GBDT

mode="gbdt" 使用 Rust 原生 XGBoost-like 直方图梯度提升训练器。训练器支持数值特征、原生类别特征、回归目标、二分类目标,以及 Objective.log_loss() / Objective.cross_entropy_loss() 多分类:

model = BinningTransformer(features=["x1", "x2"], target="y").fit(
    df,
    mode="gbdt",
    objective=Objective.log_loss(),
    mapping=[Stat.bin_value(), Stat.gain()],
    extra_params={
        "n_estimators": 100,
        "learning_rate": 0.1,
        "max_depth": 3,
        "max_leaves": 16,
        "max_bin": 64,
        "min_child_weight": 1e-3,
        "min_samples_leaf": 2,
        # 或使用 "min_samples_fraction": 0.02 按训练样本数比例设置叶子最小样本数
        "lambda_l1": 0.0,
        "lambda_l2": 1.0,
        "gamma": 0.0,
        "boosting_type": "gbdt",
        "subsample": 0.9,
        "colsample_bytree": 1.0,
        "top_rate": 0.2,
        "other_rate": 0.1,
        "enable_bundle": False,
        "bundle_concentration_threshold": 0.95,
        "bundle_conflict_threshold": 0.05,
        "categorical_features": ["x2"],
        "monotone_constraints": {"x1": 1},
        "missing_policy": "auto",
        "seed": 42,
    },
)

probability = df.select(model.predict_(output="probability"))
importance = model.feature_importance()

训练器使用 objective.rs 中的原生 loss 枚举统一计算初始分数、梯度和海森矩阵。 数值特征按分位点构建 histogram split;类别特征先编码为稳定类别 code,再按类别 leaf score 排序并扫描有序前缀作为候选集合,避免枚举指数级子集。缺失值使用 XGBoost-like default direction;数值特征支持单调约束候选过滤和子节点预测边界传播。 lambda_l1/lambda_l2 分别控制叶权重的 L1/L2 正则;boosting_type="goss" 启用大梯度单边采样,此时不能同时设置 subsample < 1.0,并通过 top_rate/other_rate 控制大梯度保留和小梯度采样比例。enable_bundle=True 启用训练期 EFB/Bundle 优化:对高集中度且低冲突度的数值特征桶分组调度, 导出的树仍使用原始特征名,类别特征暂不参与 Bundle。 完整 wine 多分类示例见 examples/example_gbdt.py

普通 optbin_nd 表达式已移除;需要多维特征切分时,建议使用原生 GBDT 训练器,让多特征交互由树分裂自动学习。

树规则暴露 Stat.gain(),定义为从根到叶子路径上的累积分裂增益。feature_importance() 则对每个内部分裂恰好计数一次,返回 featuregaingain_normalizedsplit_count。结构化的规则编辑会使原始树的重要性信息失效。

原生随机森林

mode="random_forest" 提供 Rust 原生随机森林后端,复用同一套树规则、预测和特征重要性接口。

forest = BinningTransformer(features=["x1", "x2"], target="y").fit(
    df,
    mode="random_forest",
    objective=Objective.log_loss(),
    mapping=[Stat.bin_value(), Stat.gain()],
    extra_params={
        "n_trees": 100,
        "max_depth": 6,
        "min_samples_leaf": 2,
        "m": 4,
        "bootstrap": True,
        "categorical_features": ["x2"],
        "seed": 42,
    },
)

原生 RF 支持回归、二分类、多分类、缺失默认方向、类别 one-vs-rest split、bootstrap/subsample 和特征子采样。wine 数据集示例见 examples/example_wine_random_forest.py

当前范围

  • 通过 BinningTransformer.fit 的多特征优化 1D 分箱。
  • 类型化的 ObjectiveStat 选择器。
  • 即时 transform(df) 和表达式友好的 transform_()
  • summary() 以 Polars DataFrame 格式返回拟合的规则统计量。
  • 通过 mode="pairwise_2d" 的两两 2D 矩形分箱。
  • XGBoost-like 原生直方图 GBDT 训练、树规则转换及模型预测。
  • 原生随机森林训练、树规则转换及模型预测。
  • XGBoost/LightGBM 数值树解析、叶子规则转换及模型预测。

ND 优化拟合在可复用规则引擎接口稳定期间暂时搁置。

发布

发布脚本会验证 pyproject.tomlCargo.toml 使用相同的版本号,构建优化后的 wheel 和源码分发包,发布到 PyPI,创建对应的 v<version> Gitee 标签,并将文件同步到 Gitee Release。

推荐使用 Token 认证:

$env:GITEE_TOKEN = "<personal-access-token>"
$env:UV_PUBLISH_TOKEN = "<pypi-api-token>"
python scripts/gitee_release.py

发布脚本支持 --phase 参数,默认 all 会先构建分发包,再把 dist 中的产物上传到 PyPI 和同步到 Gitee。--phase build 仅构建产物;--phase pypi--phase gitee 都只从 dist 读取已有文件并执行上传,适合单独重试某个上传阶段:

python scripts/gitee_release.py --phase build
python scripts/gitee_release.py --phase pypi
python scripts/gitee_release.py --phase gitee

PYPI_API_TOKEN 可作为别名使用。也支持账号/密码认证,通过 MATURIN_USERNAME + MATURIN_PASSWORDPYPI_USERNAME + PYPI_PASSWORD

PyPI 分发包是不可变的。重复运行时,--skip-existing 会跳过已发布的文件,同时允许上传其他平台对应的 wheel。Gitee Release 会更新,同名附件会被替换。已有 wheel 会被保留。

WHEEL_DIR 可设置构建和上传使用的目录,默认为 distSKIP_PYPI=1SKIP_GITEE=1 仍可作为 CI 兼容开关覆盖阶段执行。所有凭证必须存储在 CI 服务的安全变量存储中。

发布检查清单:

  1. 同时更新 pyproject.toml 和根 Cargo.toml[workspace.package] 版本号(crates/python/Cargo.toml 继承该版本)。
  2. 更新 CHANGELOG.md
  3. 运行 Clippy 和 Python 测试套件。
  4. 在应打版本标签的提交上运行发布脚本。

项目政策请参考 CONTRIBUTING.mdSECURITY.mdLICENSE

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polars_optbinning-0.4.1.tar.gz (140.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

polars_optbinning-0.4.1-cp39-abi3-win_amd64.whl (5.7 MB view details)

Uploaded CPython 3.9+Windows x86-64

File details

Details for the file polars_optbinning-0.4.1.tar.gz.

File metadata

  • Download URL: polars_optbinning-0.4.1.tar.gz
  • Upload date:
  • Size: 140.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for polars_optbinning-0.4.1.tar.gz
Algorithm Hash digest
SHA256 23a19baf7df2982d7dbf648a6f339ca2ecf8cdf410641ba77947d9ba827b3e73
MD5 f492f1fcf3ecca8702c1d5308726fb06
BLAKE2b-256 db172c57f75b975596ad810f791e107a858fe90d300327fe201a156f40bad45d

See more details on using hashes here.

File details

Details for the file polars_optbinning-0.4.1-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: polars_optbinning-0.4.1-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: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for polars_optbinning-0.4.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 8a2b66549c4f36c3a008b4ee9a37d9e9111bb3cdb267725af6a123c7fac70911
MD5 aa4be6f137606efca8f2fc5eed4ddfea
BLAKE2b-256 95a8c26bf38dec00f320dea3d9cc508a348cc9859d3034233ebd9e1bc9e374a2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page