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
//! Error type shared across every splitter and evaluation utility.

use thiserror::Error;

/// Errors returned by splitters and evaluation utilities.
///
/// The library follows a deliberate policy on degenerate inputs (mirroring the
/// convention used across the sibling `imbalance-rs` crate):
///
/// * **Warn and adjust** where a sane fallback exists — e.g. a
///   [`StratifiedKFold`](crate::splitters::StratifiedKFold) class smaller than
///   `n_splits` is distributed across as many folds as it can fill, with a
///   warning on stderr, rather than aborting the whole split.
/// * **Hard-error** where there is no meaningful fallback — e.g. a
///   [`TimeSeriesSplit`](crate::splitters::TimeSeriesSplit) asked for more
///   splits than the data can supply a valid train/test window for.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum ModelSelectionError {
    /// Fewer samples than are needed to form the requested splits.
    #[error("not enough samples: need at least {needed}, got {got}")]
    NotEnoughSamples {
        /// Minimum number of samples the configuration requires.
        needed: usize,
        /// Number of samples actually supplied.
        got: usize,
    },

    /// A class label had no samples where at least one was required.
    #[error("class has no samples but was required to be non-empty")]
    EmptyClass,

    /// A group had no samples where at least one was required.
    #[error("group has no samples but was required to be non-empty")]
    EmptyGroup,

    /// The requested number of splits is invalid (must be `>= 2`, and no larger
    /// than the number of samples / groups the splitter partitions over).
    #[error("invalid split count: {msg}")]
    InvalidSplitCount {
        /// Human-readable explanation of why the count is invalid.
        msg: String,
    },

    /// [`TimeSeriesSplit`](crate::splitters::TimeSeriesSplit) could not carve
    /// out a training window large enough for every requested split.
    #[error("insufficient training window: {msg}")]
    InsufficientTrainWindow {
        /// Human-readable explanation of the shortfall.
        msg: String,
    },

    /// Two inputs that had to agree on length did not (e.g. a stored label
    /// array whose length differs from `n_samples` passed to `split`).
    #[error("shape mismatch: expected length {expected}, got {got}")]
    ShapeMismatch {
        /// Length that was expected.
        expected: usize,
        /// Length that was supplied.
        got: usize,
    },
}

/// Convenience alias for results returned throughout this crate.
pub type Result<T> = std::result::Result<T, ModelSelectionError>;