Skip to content

Decision trees

Cargo featurefeatures = ["ml"]

A DecisionTree is a trained tree given as data: its nodes with the root first, the names of its features and of its classes. DecisionNode::Split { feature, threshold, left, right } sends a sample whose feature is at most threshold to the node left, the others to right; DecisionNode::Leaf { class } predicts a class. predict(&sample) follows the tests. A child index out of range or a node reached twice ends that branch.

  • DecisionTreeDiagram. The tree with the root at the top, level pixels between levels. A split is a box with its test (petal length ≤ 2.45) and a leaf a box with its class, in that class’s colour from class_colors. The edge to the left child is labelled yes and the other no. The boxes are as wide as the longest text and at least node_width.
  • DecisionSurface. The feature space of axes (an AxesMap, as in NormalFit) cut into the regions of the leaves, each filled in its class’s colour at opacity, with the cuts drawn in line_color. x_feature and y_feature are the features along the axes; the tree should split on these two only, because a split on another feature gives both its sides its whole region. Show it before the Axes so their lines stay on top.
  • Growing. reveal: Some(TreeReveal { order, start, step }) shows a level (RevealOrder::LevelByLevel) or a node (RevealOrder::BreadthFirst) every step seconds from start. Give the diagram and the surface the same reveal: a cut appears with its split and a region with its leaf.

Gallery scenes: DecisionTreeDiagram, DecisionSurface, DecisionTree (the iris tree beside its feature space).

DecisionTreeDiagram demo, first frame
DecisionTreeDiagram 5 s
DecisionSurface demo, first frame
DecisionSurface 5 s
DecisionTree demo, first frame
DecisionTree 5 s

Build the tree once, then pass it to either drawing. Both use the same reveal so their nodes, cuts and regions appear together.

use fframes_kit::prelude::*;
fn iris_tree() -> DecisionTree<'static> {
DecisionTree {
nodes: vec![
DecisionNode::Split { feature: 0, threshold: 2.45, left: 1, right: 2 },
DecisionNode::Leaf { class: 0 },
DecisionNode::Split { feature: 1, threshold: 1.75, left: 3, right: 4 },
DecisionNode::Leaf { class: 1 },
DecisionNode::Leaf { class: 2 },
],
features: vec!["petal length", "petal width"],
classes: vec!["setosa", "versicolor", "virginica"],
}
}
fn tree_diagram<'a>(ui: &mut Ui<'a, '_, '_>, tree: &DecisionTree<'a>) -> Svgr<'a> {
ui.show(DecisionTreeDiagram {
tree: tree.clone(),
reveal: Some(TreeReveal { order: RevealOrder::BreadthFirst, start: 0.7, step: 0.6 }),
..ui.defaults()
})
}
fn tree_surface<'a>(ui: &mut Ui<'a, '_, '_>, tree: &DecisionTree<'a>) -> Svgr<'a> {
let axes = Axes {
x_range: Range::new(0.0, 7.0, 1.0),
y_range: Range::new(0.0, 3.0, 0.5),
x_label: Some("petal length".into()),
y_label: Some("petal width".into()),
..ui.defaults()
};
let surface = ui.show(DecisionSurface {
tree: tree.clone(),
axes: axes.map(ui),
reveal: Some(TreeReveal { order: RevealOrder::BreadthFirst, start: 0.7, step: 0.6 }),
..ui.defaults()
});
let axes = ui.show(axes);
svgr!(<g>{surface}{axes}</g>)
}

Components are plain structs. Set the fields you need and fill the rest with ..ui.defaults(): the defaults below come from the current theme (theme. values are fields of Theme).

A decision tree as ManimML draws it (DecisionTreeDiagram): each split a box with its test (feature ≤ threshold), each leaf a box with its class in that class’s colour from class_colors, the root at the top, level pixels between levels and gap pixels between neighbouring leaves. The edge to the left child, the samples that pass the test, is labelled yes. The boxes are as wide as the longest text and at least node_width. With reveal the tree grows a level or a node at a time.

Field Type Default Description
tree DecisionTree DecisionTree::default() The splits and leaves to draw, with the feature and class names.
center (f64, f64) (0.0, 0.0) The middle of the tree, in units of ui.space.
node_width f32 190.0 Minimum width in pixels of a node box; boxes grow to fit the longest text.
node_height f32 54.0 Height in pixels of a node box.
gap f32 24.0 Pixels between neighbouring node boxes.
level f32 110.0 Pixels between one level of the tree and the next.
reveal Option<TreeReveal> None Grows the tree a level or a node at a time; None shows it whole.
class_colors Vec<Color> class_colors(theme) Colour of each class, by class index, for the leaf boxes; a class past the end uses node_color.
node_color Color theme.colors.text Colour of the split boxes’ outline and of all node text.
node_fill Color theme.colors.surface Fill colour of the node boxes (leaves mix in their class colour).
edge_color Color theme.colors.muted Colour of the edges and their yes/no labels.
text_style TextStyle theme.typography.caption The font of the node text and edge labels: family, size in pixels and weight.
stroke_width f32 2.5 Width in pixels of the node outlines and edges.
enter Option<FadeIn> None Fade in at the start; None for none.
exit Option<FadeOut> None Fade out before the end; None for none.

Methods:

  • fn layout_at(&self, middle: Point) -> Vec<Option<Rect>>: The box of every node with the middle of the tree at middle, node_width pixels wide; None for the nodes not reached from the root.
  • fn layout(&self, ui: &Ui) -> Vec<Option<Rect>>: The box of every node, in pixels, all as wide as the longest text needs and at least node_width.

Feature ml · Source

The partition of a 2D feature space by a decision tree (ManimML’s DecisionTreeSurface): every leaf’s region of the box of axes filled in its class’s colour at opacity, and the cuts of the splits drawn in line_color. x_feature and y_feature are the features along the axes; the tree should split on these two only. With the reveal of a DecisionTreeDiagram a cut appears with its split and a region with its leaf. Draw the Axes after it to keep their lines on top.

Field Type Default Description
tree DecisionTree DecisionTree::default() The splits and leaves whose partition is drawn.
axes AxesMap AxesMap::default() The axes of the feature space to fill, mapping feature values to the canvas.
x_feature usize 0 Index of the feature along the x-axis.
y_feature usize 1 Index of the feature along the y-axis.
reveal Option<TreeReveal> None A cut appears with its split and a region with its leaf; None shows everything.
class_colors Vec<Color> class_colors(theme) Colour of each class, by class index, for the regions; a class past the end uses line_color.
opacity f32 0.35 0 transparent to 1 opaque, of the filled regions.
line_color Color theme.colors.text Colour of the cuts drawn for the splits.
stroke_width f32 2.5 Width in pixels of the cut lines.
enter Option<FadeIn> None Fade in at the start; None for none.
exit Option<FadeOut> None Fade out before the end; None for none.

Feature ml · Source