segmtree
可解释的无监督决策树分群库 —— 把一张"客户标签"表自动划分成若干人群,每个人群都由一条 简短、人类可读的规则链定义,全程不需要任何标签 y。
segmtree 实现的是聚类决策树(clustering tree):不预测类别,而是自顶向下递归二分,
每一刀选择使"组内异质性下降最多"的单特征阈值规则,并按 Best-First(全局收益最大的叶子
先切)生长。可以理解为"没有 y 也能长的决策树"——同时给你划分结果和划分规则,这是
k-means / GMM 做不到的。
为什么选 segmtree?
| k-means / GMM | segmtree | |
|---|---|---|
| 人群定义 | 质心坐标 | income > 50k AND is_active = 0 式规则 |
| 新样本归类 | 最近质心 | 至多几条阈值判断即可回放 |
| 二值 + 连续混合特征 | 需要各种缩放技巧 | 归一化后同口径比较 |
| 内置自检 | 无 | 收益可加性 + 规则回放一致性校验 |
| 稳定性评估 | 自己想办法 | 一行调用的 Bootstrap ARI |
典型场景:带行为标签的客户分群、患者表型归类、问卷人群画像——一切"大量 0/1 标签 + 少量计数/金额列"的表。
安装
pip install segmtree
仅要求 Python ≥ 3.9 与 numpy。
快速上手
import numpy as np
from segmtree import SegTree, extract_rules, profile_leaves, replay_check, bootstrap_stability
rng = np.random.default_rng(0)
X = np.vstack([
np.hstack([rng.normal([0, -1, 2, .5], .5, (250, 4)), (rng.random((250, 3)) < .1)]),
np.hstack([rng.normal([4, 3, -2, .5], .5, (200, 4)), (rng.random((200, 3)) < .5)]),
np.hstack([rng.normal([-4, 2, 0, 8], .5, (150, 4)), (rng.random((150, 3)) < .9)]),
])
tree = SegTree(min_gain=0.02, min_leaf_frac=0.05, min_leaf_abs=10)
tree.fit(X)
print(f"{tree.n_leaves_} 类人群,异质性降低 {tree.reduction_:.0%}")
for rule in extract_rules(tree):
print(rule) # #0: x1 <= -1.24 AND x0 <= 1.83 ...
profile_leaves(tree, X, top_k=3) # 每类与全体差异最大的特征(z 分数)
replay_check(tree, X) # True:规则回放与 predict 完全一致
bootstrap_stability(tree, X, n_replicates=12, random_state=0).mean # 例如 0.98
取值只有 {0, 1} 的列会被自动识别为二值特征(只做一次"是/否"切分);直接传 pandas DataFrame 也可以,列名会自动带入规则文本。
类别特征
整数编码的类别列(如名义变量的 0..k-1 码)可通过 categorical_features 声明。
它们不按阈值切分,而是按类别子集划分,规则呈现为成员判断:
tree = SegTree(categorical_features=["color"], max_categories=12).fit(df)
# 规则示例: #3: color in {1, 2} AND income > 42 ...
异质性贡献:类别列使用 Gini 杂度相对根 Gini 的比值,连续/二值列保持归一化 方差。Gini 只取决于类别分布,因此对编码做任何一一重映射都会得到等价的划分—— 结果与编码方式无关。
注意:
- 编码必须为整数值;非数值标签请先因子化
(
pd.factorize或df["color"].astype("category").cat.codes); - 任意大小的编码都会被精确处理(内部 int64/float64),即使连续列选择
dtype=np.float32也不会合并; - 候选子集:默认
categorical_splitter="exact"会精确评估节点内全部2^(k-1)-1种类别划分(指数复杂度,由max_categories把关)。高基数列请改用categorical_splitter="one_vs_rest":逐个评估"单类别 vs 其余",代价随 k 线性 增长,且不再受max_categories限制——但可能错过"多个类别合并到一侧"的最优切分。 - 预测时未见过的编码由
handle_unknown控制:"complement"(默认)确定性地 落入补集一侧且replay_check依然严格一致;"error"抛出带特征名与未知值 的ValueError。训练期类别集合通过categories_属性暴露; profile_leaves通过category_enrichment((特征, 编码, 叶内占比, lift))报告类别结构,z 分数仅用于连续/二值特征。
方法原理
节点异质性是各特征杂度比值的加权平均:
H(S) = Σ_j w_j · Var_j(S)/Var_j(root) / Σ_j w_j H(root) = 1
未传 feature_weights 时所有 w_j = 1。二值特征的伯努利方差 p(1−p) 天然适用
同一公式;声明为类别的列改用 Gini 杂度相对根 Gini 的比值。一刀切成 L/R 的收益:
Gain = H(S) − [ n_L/n_S · H(L) + n_R/n_S · H(R) ]
候选阈值取各特征在节点内的分位数(quantiles=8 即 5%~95% 共 8 个;二值特征仅 0.5
一刀)。每个特征用排序 + 前缀和 O(n log n) 批量评估全部候选;frontier 放在以
负 gain 为键的最小堆里,因此每次扩展都取当前全局收益最大的叶子(Best-First,
同收益按插入顺序稳定决胜)。满足任一条件即停止:最优刀收益低于 min_gain、子节点
过小(max(min_leaf_abs, min_leaf_frac·n))、达到 max_depth,或叶数已达
max_leaf_nodes(每刀恰好新增一个叶子,预算耗尽后剩余候选节点直接成为叶子)。
两个开箱即用的可信度保证:
- 收益可加性 ——
total_gain_ == h_root_ − h_avg_(浮点误差量级),任意合法 权重下同样精确成立; - 规则回放 ——
replay_check()从零重新求值每条叶子规则链,必须与predict()逐行一致。
测试套件还包含与一个独立朴素参考实现逐位一致性的对照测试。
特征加权
业务上常常"某些列更重要"。feature_weights 接受长度等于特征数的一维数值序列、
按列名索引的 dict(未列出的列权重为 1),或默认 None(等权重):
tree = SegTree(feature_weights={"income": 4.0, "churn_flag": 2.0}).fit(df)
权重缩放各列对异质性与收益的贡献——它并不禁止在零权重列上切分:只要零权重列能很好 地分开其他被加权的特征,仍可作为代理变量被选中。
导出拟合好的树
d = tree.export_dict() # 可直接 JSON 序列化的嵌套 dict
import json; json.dumps(d) # 开箱即用
print(tree.export_text()) # 缩进文本:分支条件、gain、样本数、叶子编号
split 节点包含 feature/feature_index/op/threshold|categories/gain/n_samples/ left/right;leaf 节点包含 leaf_id/n_samples/fraction/heterogeneity/conditions。
不暴露任何私有对象。想出图可自行把 dict 转成 graphviz 等格式(segmtree 不引入
额外运行时依赖),README 英文版附有转换示例。
API 一览
SegTree(min_gain=0.01, min_leaf_frac=0.01, min_leaf_abs=50, max_depth=None,
quantiles=8, binary_features="auto", categorical_features=None,
max_categories=12, handle_unknown="complement", feature_names=None,
dtype=np.float64, feature_weights=None, max_leaf_nodes=None,
categorical_splitter="exact")
| 成员 | 用途 |
|---|---|
.fit(X) / .fit_predict(X) / .predict(X) |
sklearn 风格训练与归类 |
.labels_, .n_leaves_, .leaves_, .cuts_ |
拟合结构 |
.reduction_, .h_avg_, .total_gain_, .summary() |
质量统计 |
.feature_weights_ |
生效的逐特征权重 |
.export_dict() / .export_text() |
公开树导出(JSON 安全 dict / 文本) |
extract_rules(tree) |
返回 Rule 列表(str(rule) 即规则链文本) |
replay_labels(tree, X) / replay_check(tree, X) |
规则回放校验 |
profile_leaves(tree, X, top_k=3) |
每类 z 分数画像 |
bootstrap_stability(tree, X, ...) |
Bootstrap ARI(mean/std) |
adjusted_rand_score(a, b) |
纯 numpy 实现的 ARI |
调参建议:
- 想要更少、更大的人群 → 提高
min_gain(如 0.02~0.05)、减少quantiles; 已知目标人群数时直接设max_leaf_nodes; - 规则想更短、结果想更稳 → 设
max_depth(4~6); - 小数据集(< 5000 行)→ 降低
min_leaf_abs(默认 50 是为大数据设的下限); - 有业务侧重点 → 用
feature_weights强调关键列,而不是删掉其余特征; - Bootstrap ARI 偏低 → 通常减少分位数、提高
min_gain、限深或限叶数即可稳定结构。
与相关工具的比较
- 先 k-means 再用 sklearn 决策树拟合(代理树):两步走,规则可能与聚类结果矛盾; segmtree 把切分质量与规则一致性放进同一个目标。
- forest-clustering / URF:基于森林邻近度的聚类,精度好但人群本身不可直接解释。
- CUBT(R 包):学术上最接近的方法;segmtree 在纯 numpy 包内补齐了 Best-First 生长、混合类型归一化、可加性自检与 Bootstrap 稳定性。
开发
git clone <your-fork-url> && cd segmtree
pip install -e .[dev]
pytest # 运行测试
ruff check src tests
许可证
MIT — 见 LICENSE。
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 segmtree-0.4.0.tar.gz.
File metadata
- Download URL: segmtree-0.4.0.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16e2635a41f46e2686f99ca1b584c031f4b9fd8cf4dbf39d504527a64c63f73d
|
|
| MD5 |
2248394e461c08b6418b92e26fe93030
|
|
| BLAKE2b-256 |
829e25bb844558fc540563d19e2d0ed07573214a07acd7b9b268aa773eb671e5
|
Provenance
The following attestation bundles were made for segmtree-0.4.0.tar.gz:
Publisher:
release.yml on Unknownxu1/segmtree
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
segmtree-0.4.0.tar.gz -
Subject digest:
16e2635a41f46e2686f99ca1b584c031f4b9fd8cf4dbf39d504527a64c63f73d - Sigstore transparency entry: 2594569498
- Sigstore integration time:
-
Permalink:
Unknownxu1/segmtree@2319eb20182d7a1d24faf936bd2b5f1cf6a53e39 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/Unknownxu1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2319eb20182d7a1d24faf936bd2b5f1cf6a53e39 -
Trigger Event:
push
-
Statement type:
File details
Details for the file segmtree-0.4.0-py3-none-any.whl.
File metadata
- Download URL: segmtree-0.4.0-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bce23c125915f853488c9ba1121ac23f39258adacae97ec82df3a6af86426956
|
|
| MD5 |
9e353db672861a7bea099d3efdd594c2
|
|
| BLAKE2b-256 |
0bdfa6d65b718c0d808cc32275db0dfbd9a7c760e6fa8ad87305fc36fb440cc3
|
Provenance
The following attestation bundles were made for segmtree-0.4.0-py3-none-any.whl:
Publisher:
release.yml on Unknownxu1/segmtree
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
segmtree-0.4.0-py3-none-any.whl -
Subject digest:
bce23c125915f853488c9ba1121ac23f39258adacae97ec82df3a6af86426956 - Sigstore transparency entry: 2594570000
- Sigstore integration time:
-
Permalink:
Unknownxu1/segmtree@2319eb20182d7a1d24faf936bd2b5f1cf6a53e39 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/Unknownxu1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2319eb20182d7a1d24faf936bd2b5f1cf6a53e39 -
Trigger Event:
push
-
Statement type: