model-selection-rs 0.1.0

Cross-validation and model-selection utilities for Rust: stratified / group-aware / time-series splitting, nested CV, and learning & validation curves. Dependency-light, composes with any modeling crate.
Documentation
//! # 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
//! ```

#![warn(missing_docs)]
#![forbid(unsafe_code)]

pub mod error;
pub mod evaluate;
pub mod scoring;
pub mod splitters;

pub use error::{ModelSelectionError, Result};