Skip to main content

model_selection_rs/
lib.rs

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