use millwright::grid;
use millwright::prelude::*;
fn main() -> Result<()> {
let (train, probe) = make_data()?;
let pipe = Pipeline::new()
.step("impute", SimpleImputer::median())
.step("scale", StandardScaler::new())
.balance(Smote::new().k_neighbors(3).random_state(0))
.estimator("rf", RandomForest::new());
let search = GridSearch::new(pipe, grid! { "rf__max_depth" => [2, 4, 8] })
.cv(StratifiedKFold::new(4))
.scoring(Metric::F1)
.fit(&train)?;
println!("grid search:");
for (params, score) in search.leaderboard() {
println!(" {score:.3} {params:?}");
}
println!(" best F1 = {:.3}", search.best_score());
println!(" predictions = {:?}", search.predict(&probe)?);
let mut vote = Voting::soft()
.add("rf_shallow", RandomForest::new().max_depth(2))
.add("rf_deep", RandomForest::new().max_depth(8));
vote.fit(&train)?;
println!("soft-vote predictions = {:?}", vote.predict(&probe)?);
let mut stack = Stacking::meta(RandomForest::new().n_trees(50))
.base("rf", RandomForest::new().n_trees(30))
.base("rf2", RandomForest::new().max_depth(3))
.cv(StratifiedKFold::new(4));
stack.fit(&train)?;
println!("stacking predictions = {:?}", stack.predict(&probe)?);
println!("ok — a real, tunable, ensemble-ready workflow.");
Ok(())
}
fn make_data() -> Result<(Dataset, Frame)> {
let cols = vec!["a".to_string(), "b".to_string()];
let mut rows = Vec::new();
let mut y = Vec::new();
for i in 0..24 {
rows.push(vec![i as f64 * 0.1, i as f64 * 0.1]);
y.push(0.0);
}
for i in 0..8 {
rows.push(vec![9.0 + i as f64 * 0.1, 9.0 + i as f64 * 0.1]);
y.push(1.0);
}
rows[0][0] = f64::NAN; let train = Dataset::new(Frame::from_rows(rows, cols.clone())?, y)?;
let probe = Frame::from_rows(vec![vec![0.3, 0.2], vec![9.4, 9.3]], cols)?;
Ok((train, probe))
}