Getting started#
RuleTree builds interpretable decision trees one node at a time. Each node is split by a stump:
a single split, such as a threshold on a feature (petal length <= 2.45), the distance from an
instance of the training set, or the distance of a time series from a shapelet. The same tree can
mix different kinds of stumps and different types of data.
The trees follow the scikit-learn API: fit, predict, predict_proba, get_params,
set_params, and they work with the scikit-learn tools for model selection.
Installation#
RuleTree requires Python 3.10 or later. Install it from PyPI, or from the root of the repository
with pip install .:
pip install RuleTree
Some features need packages that are not installed by default. They are installed by the extras of RuleTree:
pip install "RuleTree[shapelets]" # shapelet, proximity and partial pivot stumps (numba)
pip install "RuleTree[graphviz]" # drawing the trees (also requires the Graphviz binaries)
pip install "RuleTree[kmedoids]" # shapelets selected with k-medoids (scikit-learn-extra)
pip install "RuleTree[all]" # all of the above
pip install "RuleTree[test]" # running the tests
Without them, the rest of the library works, and the features that need them raise a
MissingOptionalDependencyError with the command to run.
Classification#
RuleTreeClassifier grows the tree breadth-first until
the nodes are pure or a limit is reached (max_depth, max_leaf_nodes,
min_samples_split). Each leaf predicts the majority class of its training instances.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from RuleTree import RuleTreeClassifier
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.3,
random_state=42, stratify=iris.target)
model = RuleTreeClassifier(max_depth=3, random_state=42)
model.fit(X_train, y_train)
print("accuracy:", round(model.score(X_test, y_test), 3))
print(model.predict(X_test[:5]))
print(model.predict_proba(X_test[:2]).round(2))
accuracy: 0.933
[2 1 2 1 2]
[[0. 0.03 0.97]
[0. 1. 0. ]]
random_state makes the tree reproducible: when two features split a node equally well, the
choice between them is not random.
The rules of the tree can be printed with the names of the features:
model.print_rules(model.get_rules(columns_names=iris.feature_names))
|--- petal length (cm) <= 2.45
| output: 0
|--- petal length (cm) > 2.45
| |--- petal width (cm) <= 1.55
| | |--- petal length (cm) <= 4.95
| | | output: 1
| | |--- petal length (cm) > 4.95
| | | output: 2
| |--- petal width (cm) > 1.55
| | |--- petal length (cm) <= 4.85
| | | output: 1
| | |--- petal length (cm) > 4.85
| | | output: 2
See Reading the tree for the other ways to read a tree.
Regression#
RuleTreeRegressor works in the same way; each leaf
predicts the mean of the target of its training instances.
from sklearn.datasets import load_diabetes
from sklearn.metrics import r2_score
from RuleTree import RuleTreeRegressor
X, y = load_diabetes(return_X_y=True)
regressor = RuleTreeRegressor(max_depth=3, random_state=42).fit(X, y)
print("R2 on the training set:", round(r2_score(y, regressor.predict(X)), 3))
print("leaves:", len(regressor.get_leaf_nodes()))
R2 on the training set: 0.501
leaves: 8
Clustering#
RuleTreeCluster finds clusters described by rules, without
a target. To split a node, it projects its instances on their principal components and splits
the component that is best separated; a node is split only if the split improves the Bayesian
Information Criterion of the clustering (bic_eps makes this stricter).
from sklearn.datasets import make_blobs
from sklearn.metrics import adjusted_rand_score
from RuleTree import RuleTreeCluster
X, groups = make_blobs(n_samples=300, centers=3, cluster_std=0.5, random_state=0)
clustering = RuleTreeCluster(max_depth=3, random_state=0)
labels = clustering.fit_predict(X)
print("clusters:", sorted(set(labels.tolist())))
print("agreement with the true groups (ARI):", round(adjusted_rand_score(groups, labels), 2))
clusters: [4, 5, 6, 7, 8]
agreement with the true groups (ARI): 0.95
The labels identify the leaves, numbered in breadth-first order (the root is 0). Here the tree
splits two of the three groups once more, so there are five clusters; the agreement with the
groups stays high because each cluster lies within a single group.
RuleTreeClusterClassifier and
RuleTreeClusterRegressor build the tree in the same way,
without looking at the target, and then predict the majority class or the mean target of each
cluster.
Working with scikit-learn#
The trees are scikit-learn estimators, so they can be used in pipelines, cross-validation and hyper-parameter searches:
from sklearn.model_selection import GridSearchCV, cross_val_score
scores = cross_val_score(RuleTreeClassifier(max_depth=3, random_state=0), iris.data, iris.target, cv=5)
print("cross-validated accuracy:", round(scores.mean(), 3))
search = GridSearchCV(RuleTreeClassifier(random_state=0), {"max_depth": [1, 2, 3, 4]}, cv=5)
search.fit(iris.data, iris.target)
print("best max_depth:", search.best_params_["max_depth"])
cross-validated accuracy: 0.973
best max_depth: 3
Next steps#
Choosing the splits: stumps: the available stumps and how to combine them.
Tabular data, time series and images: trees on time series and images, alone or together with tabular data.
Reading the tree: rules, Graphviz drawings, leaves and feature contributions.
Saving, growing and updating trees: saving and loading trees, growing them further, updating their statistics.
Ensembles: gradient boosting and explainable boosting with RuleTrees.
Evaluating the models: metrics of the predictions, of the clusters and of the size of the trees.
Extending the library: writing new stumps.