1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! # model-selection-rs
//!
//! Cross-validation and model-selection utilities for Rust, filling the specific
//! gaps in `smartcore::model_selection`: **stratified**, **group-aware**, and
//! **time-aware** splitting, **nested** cross-validation, and **learning /
//! validation curve** utilities — as a standalone, dependency-light crate rather
//! than hand-rolled notebook code.
//!
//! The crate is deliberately about *splitting strategies* and *evaluation-loop
//! utilities*, **not** hyperparameter search itself. Grid/random search and
//! Bayesian optimizers (e.g. the `tpe` crate) remain their own job; this crate
//! composes with them — most directly through
//! [`nested_cross_validate`](evaluate::nested_cross_validate), whose tuning step
//! is a closure you fill with whatever search you like.
//!
//! ## Layout
//!
//! * [`splitters`] — every splitter, all implementing the one
//! [`CvSplitter`](splitters::CvSplitter) trait.
//! * [`scoring`] — the [`Scorer`](scoring::Scorer) trait, built-in metrics, and
//! (behind the `smartcore-metrics` feature) adapters over
//! `smartcore::metrics`.
//! * [`evaluate`] — [`cross_validate`](evaluate::cross_validate),
//! [`nested_cross_validate`](evaluate::nested_cross_validate),
//! [`learning_curve`](evaluate::learning_curve), and
//! [`validation_curve`](evaluate::validation_curve).
//!
//! ## Feature flags
//!
//! * `parallel` — fan fold execution out over `rayon` in the `evaluate`
//! utilities. Public signatures are unchanged; only wall-clock time differs,
//! and results are numerically identical to the serial path.
//! * `smartcore-metrics` — enable [`scoring::smartcore_adapter`], wrapping
//! `smartcore::metrics` (F1, ROC-AUC) as [`Scorer`](scoring::Scorer)s.
//!
//! ## Quick start
//!
//! ```
//! use ndarray::{Array1, Array2};
//! use model_selection_rs::evaluate::{cross_validate, BoxedScorer};
//! use model_selection_rs::scoring::R2Score;
//! use model_selection_rs::splitters::KFold;
//!
//! // Toy: perfectly linear data, a least-squares "model".
//! let x = Array2::from_shape_fn((20, 1), |(i, _)| i as f64);
//! let y = x.column(0).mapv(|v| 2.0 * v + 1.0);
//!
//! let kf = KFold::new(5).unwrap();
//! let scorers: Vec<BoxedScorer> = vec![Box::new(R2Score)];
//! let res = cross_validate(&kf, &x, &y, |xt, yt| {
//! // fit y = a*x + b by ordinary least squares on one feature
//! let n = xt.nrows() as f64;
//! let (xs, ys) = (xt.column(0).to_owned(), yt.to_owned());
//! let mx = xs.sum() / n; let my = ys.sum() / n;
//! let cov = xs.iter().zip(ys.iter()).map(|(a,b)| (a-mx)*(b-my)).sum::<f64>();
//! let var = xs.iter().map(|a| (a-mx).powi(2)).sum::<f64>();
//! let slope = cov / var; let intercept = my - slope*mx;
//! move |xq: &Array2<f64>| xq.column(0).mapv(|v| slope*v + intercept)
//! }, &scorers, false).unwrap();
//!
//! assert!((res.mean_test_score(0) - 1.0).abs() < 1e-9); // R2 ~ 1
//! ```
pub use ;