pytrees-rs
pytrees-rs learns anytime optimal decision trees. It is written mostly in Rust and comes with a Python wrapper that follows the scikit-learn API.
An optimal tree is the one with the lowest training error among all trees of
a given depth. Finding it can take a long time, so the optimal searches can run
as anytime searches: they return a good tree quickly and keep improving it
until it is proven optimal or the time limit is reached. status_ tells you
which.
| Estimator | Features | What it learns |
|---|---|---|
DL85Classifier |
binary | Optimal trees (DL8.5), for the misclassification error or your own |
ConTreeClassifier |
continuous | Optimal trees without binarising the data (ConTree) |
LGDTClassifier |
binary | Trees grown top-down, each test chosen with a depth-2 lookahead (LGDT) |
DL85Cluster |
binary | Clusterings whose clusters are the leaves of an optimal tree |
The estimators behave like any scikit-learn estimator: they can be cloned,
pickled and used in Pipeline, GridSearchCV or cross_val_score. The
fitted tree is in tree_, with scikit-learn's layout, and a row goes left
when x[feature] <= threshold.
Installation
pip install pytrees-rs
Wheels are provided for Linux, macOS and Windows, for Python 3.10 and later. To build from source you need a Rust toolchain (1.77 or later):
git clone https://github.com/haroldks/pytrees-rs.git
cd pytrees-rs
pip install .
Quick start
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from pytrees import ConTreeClassifier
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
clf = ConTreeClassifier(max_depth=3, min_sup=5).fit(X_train, y_train)
print(clf.status_) # "optimal", or "time_limit" if it ran out of time
print(clf.train_error_) # training misclassifications
print(clf.score(X_test, y_test))
print(clf.to_dot()) # the tree in Graphviz format
DL8.5 and LGDT need binary features (0 or 1). A Binarizer or
KBinsDiscretizer with one-hot output in a Pipeline takes care of that:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import KBinsDiscretizer
from pytrees import DL85Classifier
model = make_pipeline(
KBinsDiscretizer(n_bins=4, encode="onehot-dense"),
DL85Classifier(max_depth=3, min_sup=5, max_time=60),
)
model.fit(X_train, y_train)
Anytime search
fit_anytime works like fit, but calls you back each time the search finds
a better tree:
from pytrees import DL85Classifier
from pytrees.rules import DiscrepancyRule
X_bin = KBinsDiscretizer(n_bins=4, encode="onehot-dense").fit_transform(X)
clf = DL85Classifier(
max_depth=5,
heuristic="information_gain",
discrepancy=DiscrepancyRule(), # limited discrepancy search
max_time=120,
)
clf.fit_anytime(X_bin, y, callback=lambda error, seconds, status: print(seconds, error, status))
ConTreeClassifier has the same method, and use_lds=True makes its plain
fit anytime too.
Custom error functions
DL8.5 finds the tree that minimises the sum of its leaf errors, and the error
of a leaf does not have to be the number of misclassified rows. Pass your own
as error_function: it receives the class counts of a leaf (or its row
indices, with error_function_input="indices") and returns the error and the
predicted class. Class-dependent costs, sample weights and clustering
objectives (DL85Cluster works this way) all fit:
import numpy as np
y_bin = (y == 2).astype(int) # is it Iris virginica?
costs = np.array([1.0, 5.0]) # missing a virginica costs five times more
def cost_sensitive(class_counts):
counts = np.asarray(class_counts, dtype=float)
per_prediction = [(costs * counts).sum() - costs[k] * counts[k] for k in range(len(counts))]
best = int(np.argmin(per_prediction))
return per_prediction[best], best
clf = DL85Classifier(max_depth=3, error_function=cost_sensitive).fit(X_bin, y_bin)
Documentation
- User guide: installation, every estimator and its parameters, the anytime search rules, custom error functions and the command line tools.
- Rust API: the
dtrees-rsandcontree-rscrates.
Repository layout
The Python package is built from a Cargo workspace:
| Path | Contents |
|---|---|
crates/dtrees |
The dtrees-rs library: DL8.5, LGDT and the search rules, over binary features |
crates/contree |
The contree-rs library: ConTree and its anytime variant, over continuous features |
crates/dtrees-cli, crates/contree-cli |
Command line front ends |
crates/pytrees-py |
The Python bindings (pytrees._native) |
python/pytrees |
The Python package and its scikit-learn estimators |
doc |
The documentation site (mdBook) |
To work on it:
cargo test --workspace # Rust tests
pip install maturin && maturin develop # build the Python package in place
pytest python/tests # Python tests
Publications
The algorithms in this repository come from the following papers. If you use them in your work, please cite the relevant one.
- H. Kiossou, P. Schaus, S. Nijssen and V. R. Houndji.
Time Constrained DL8.5 Using Limited Discrepancy Search.
ECML PKDD 2022, LNCS 13717, pp. 443-459.
doi:10.1007/978-3-031-26419-1_27
(
DL85ClassifierwithDiscrepancyRule) - H. Kiossou, P. Schaus, S. Nijssen and G. Aglin.
Efficient Lookahead Decision Trees.
IDA 2024, pp. 133-144.
doi:10.1007/978-3-031-58553-1_11
(
LGDTClassifier) - H. Kiossou and P. Schaus.
A Generic Complete Anytime Beam Search for Optimal Decision Tree.
IDA 2026.
doi:10.1007/978-3-032-23833-7_8,
arXiv:2508.06064
(the search rules of
DL85Classifier: CA-DL8.5) - H. Kiossou, P. Schaus and S. Nijssen.
Anytime Optimal Decision Tree Learning with Continuous Features.
ECML PKDD 2026.
arXiv:2601.14765
(
ConTreeClassifierwithuse_lds=True)
They build on:
- G. Aglin, S. Nijssen and P. Schaus. Learning Optimal Decision Trees Using Caching Branch-and-Bound Search. AAAI 2020. (DL8.5; the original implementation is pydl8.5.)
- E. Demirović, A. Lukina, E. Hebrard, J. Chan, J. Bailey, C. Leckie, K. Ramamohanarao and P. J. Stuckey. MurTree: Optimal Decision Trees via Dynamic Programming and Search. JMLR 23, 2022. (The depth-2 solver.)
- C. E. Briţa, J. G. M. van der Linden and E. Demirović. Optimal Classification Trees for Continuous Feature Data Using Dynamic Programming with Branch-and-Bound. AAAI 2025. (ConTree; the original implementation is ConSol-Lab/contree.)
License
MIT; see LICENSE.
Metadata
Release files for pytrees-rs 2.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pytrees_rs-2.1.1.tar.gz | 1.1 MB | With provenance | Details for pytrees_rs-2.1.1.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| pytrees_rs-2.1.1-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details for pytrees_rs-2.1.1-cp310-abi3-win_amd64.whl |
| pytrees_rs-2.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details for pytrees_rs-2.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| pytrees_rs-2.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details for pytrees_rs-2.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| pytrees_rs-2.1.1-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details for pytrees_rs-2.1.1-cp310-abi3-macosx_11_0_arm64.whl |
| pytrees_rs-2.1.1-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details for pytrees_rs-2.1.1-cp310-abi3-macosx_10_12_x86_64.whl |
Total release size: 3.5 MB
Release files / pytrees_rs-2.1.1.tar.gz
| Download URL | pytrees_rs-2.1.1.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7fc86d4e2eca592381c04f0316a24d55bda3f200096557fb91685e0068672889
|
|
BLAKE2b-256 checksum How to use checksums |
aa6e776314c13792a4e86ba127a9283ba8224fea1d9ebfb0a35513699cdb03fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / pytrees_rs-2.1.1-cp310-abi3-win_amd64.whl
| Download URL | pytrees_rs-2.1.1-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 387.6 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
15bd591e1b7151a6233b42ca1c07d4ce2b17c321a641b5a2f4a3025b5784a994
|
|
BLAKE2b-256 checksum How to use checksums |
bf38ffd7cb18c9d730167d3efed747ae9636a20996e51b667487b2f16bbd4c41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / pytrees_rs-2.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | pytrees_rs-2.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 505.6 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
0d5611dfee11c45478462478eab5df810383cd83fde890fca756108d259f25ba
|
|
BLAKE2b-256 checksum How to use checksums |
6bdbb4f7d5209461627fb85fb2b89f3abdfa362f57ab286984e54356e6bf213b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / pytrees_rs-2.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | pytrees_rs-2.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 481.9 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
f7151658e12b3ce2ba09ee14dcfe3073fd2a02296f892c35a73ada19c478832a
|
|
BLAKE2b-256 checksum How to use checksums |
be89f7659da96d6c3f669e4a5ec7f6b903023c40176adcd7945e5511f825e2ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / pytrees_rs-2.1.1-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | pytrees_rs-2.1.1-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 457.2 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
37bf6ef51eaca4b83ff350496e4c54e3fe8d2b0f784a2aa43e1500277ee8db55
|
|
BLAKE2b-256 checksum How to use checksums |
75e6a8324c9282772269461a58445e9466d5c1f87993ebcbe3fc4977c5653800
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / pytrees_rs-2.1.1-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | pytrees_rs-2.1.1-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 490.9 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
d1a39025d99f0d4b629620f12ffdf92abf4cd5be2db21dd4aa59fc5e442aefb0
|
|
BLAKE2b-256 checksum How to use checksums |
6dea34df7fad0029ff27d6a5a3f52ac204eb8d17ab125738b32ec78f0e561e29
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logSecurity for pytrees-rs 2.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 6 files in this release have been uploaded with provenance metadata.
Attestation for pytrees_rs-2.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.