use datarust::matrix::Matrix;
use datarust::traits::{PredictProba, Predictor};
use datarust::tree::{Criterion, DecisionTreeClassifier, DecisionTreeRegressor, MaxFeatures};
use datarust::Rng;
fn label(x: f64, y: f64) -> f64 {
if (2.0..=6.0).contains(&x) && (2.0..=6.0).contains(&y) {
1.0
} else {
0.0
}
}
fn make_dataset(n: usize, seed: u64) -> (Matrix, Vec<f64>) {
let mut rng = Rng::new(seed);
let mut rows = Vec::with_capacity(n);
let mut y = Vec::with_capacity(n);
for _ in 0..n {
let (x, yy) = (rng.next_range(0.0, 8.0), rng.next_range(0.0, 8.0));
rows.push(vec![x, yy]);
y.push(label(x, yy));
}
(Matrix::new(rows).unwrap(), y)
}
fn accuracy(model: &DecisionTreeClassifier, x: &Matrix, y: &[f64]) -> f64 {
let pred = model.predict(x).unwrap();
pred.iter().zip(y).filter(|(p, t)| p == t).count() as f64 / y.len() as f64
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (x_train, y_train) = make_dataset(400, 7);
let (x_test, y_test) = make_dataset(200, 8);
println!("=== Decision tree on a rectangle rule ===");
println!(
"train: {} points, {} positive | test: {} points",
y_train.len(),
y_train.iter().filter(|&&v| v == 1.0).count(),
y_test.len()
);
let mut clf = DecisionTreeClassifier::new().with_random_state(7);
clf.fit(&x_train, &y_train)?;
let tree = clf.tree().expect("fitted");
println!("\n--- Unlimited tree ---");
println!(
"nodes: {}, depth: {}, leaves: {}",
tree.n_nodes(),
tree.get_depth(),
tree.n_leaves()
);
let f0 = tree.node_feature(0).expect("internal root");
let t0 = tree.node_threshold(0).unwrap();
println!("root split: feature {f0} <= {t0:.4}");
println!("feature importances: {:?}", clf.feature_importances()?);
println!(
"train accuracy: {:.3} | test accuracy: {:.3}",
accuracy(&clf, &x_train, &y_train),
accuracy(&clf, &x_test, &y_test)
);
let proba = clf.predict_proba(&x_test)?;
println!("\n--- predict_proba (first 3 rows) ---");
for i in 0..3 {
println!(
"row {i}: p(class 0) = {:.3}, p(class 1) = {:.3}",
proba.get(i, 0),
proba.get(i, 1)
);
}
let mut stump = DecisionTreeClassifier::new()
.with_max_depth(Some(1))
.with_random_state(7);
stump.fit(&x_train, &y_train)?;
println!("\n--- Depth-1 stump ---");
println!(
"test accuracy: {:.3} (unlimited tree: {:.3})",
accuracy(&stump, &x_test, &y_test),
accuracy(&clf, &x_test, &y_test)
);
for criterion in [Criterion::Gini, Criterion::Entropy] {
let mut c = DecisionTreeClassifier::new()
.with_criterion(criterion)
.with_random_state(7);
c.fit(&x_train, &y_train)?;
println!(
"criterion {:<8} → depth {}, test accuracy {:.3}",
criterion,
c.tree().unwrap().get_depth(),
accuracy(&c, &x_test, &y_test)
);
}
let (x, _) = make_dataset(200, 9);
let y_reg: Vec<f64> = (0..x.nrows()).map(|i| x.get(i, 0)).collect();
let mut reg = DecisionTreeRegressor::new().with_random_state(7);
reg.fit(&x, &y_reg)?;
let pred = reg.predict(&x)?;
let mse: f64 = pred
.iter()
.zip(y_reg.iter())
.map(|(p, t)| (p - t).powi(2))
.sum::<f64>()
/ y_reg.len() as f64;
println!("\n--- DecisionTreeRegressor ---");
println!("in-sample MSE reconstructing x from (x, y): {mse:.2e} (pure leaves → ~0)",);
let sqrt_tree = DecisionTreeClassifier::new()
.with_max_features(MaxFeatures::Sqrt)
.with_random_state(7);
println!(
"\nwith_max_features(Sqrt) on {} features: {}",
x_train.ncols(),
if sqrt_tree.is_fitted() {
"fitted"
} else {
"not fitted yet"
}
);
println!("\nAll trees were seeded with random_state=7 — refitting reproduces them exactly.");
Ok(())
}