Skip to main content

somatize_study/
samples.rs

1//! What is known about the samples being cut, which is never the samples.
2//!
3//! A [`Partition`](crate::Partition) is given how many there are and, when the
4//! cut needs it, one `u32` per sample for its class or its group. The labels are
5//! a tensor and the tensor stays in Python: what crosses is the class **as a
6//! number**. That is the whole reason cross-validation can be Rust without the
7//! core ever learning what a dataset is.
8
9use std::fmt;
10
11/// How many samples there are, and what is known about each. Built up, because
12/// most cuts need only the count:
13///
14/// ```
15/// use somatize_study::Samples;
16///
17/// let plain = Samples::of(100);
18/// let labelled = Samples::of(6).by_class(vec![0, 0, 1, 1, 0, 1])?;
19/// let both = Samples::of(6)
20///     .by_class(vec![0, 0, 1, 1, 0, 1])?
21///     .in_groups(vec![7, 7, 8, 8, 9, 9])?;
22/// # Ok::<(), somatize_study::SamplesError>(())
23/// ```
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Samples {
26    n: usize,
27    strata: Option<Vec<u32>>,
28    groups: Option<Vec<u32>>,
29}
30
31impl Samples {
32    /// `n` samples, with nothing known about any of them.
33    pub fn of(n: usize) -> Self {
34        Self {
35            n,
36            strata: None,
37            groups: None,
38        }
39    }
40
41    /// The class of each sample, in the same order. What
42    /// [`Stratified`](crate::Partition::Stratified) honours.
43    ///
44    /// The values are opaque: they are compared, never ordered or counted on to
45    /// start at zero. `0`/`1` and `31337`/`4` cut the same.
46    pub fn by_class(mut self, strata: Vec<u32>) -> Result<Self, SamplesError> {
47        self.check("classes", strata.len())?;
48        self.strata = Some(strata);
49        Ok(self)
50    }
51
52    /// The group of each sample, in the same order. What
53    /// [`Grouped`](crate::Partition::Grouped) keeps whole: two samples with the
54    /// same group never land on opposite sides of a fold.
55    pub fn in_groups(mut self, groups: Vec<u32>) -> Result<Self, SamplesError> {
56        self.check("groups", groups.len())?;
57        self.groups = Some(groups);
58        Ok(self)
59    }
60
61    /// How many there are.
62    pub fn n(&self) -> usize {
63        self.n
64    }
65
66    /// The class of each, if it was said.
67    pub fn strata(&self) -> Option<&[u32]> {
68        self.strata.as_deref()
69    }
70
71    /// The group of each, if it was said.
72    pub fn groups(&self) -> Option<&[u32]> {
73        self.groups.as_deref()
74    }
75
76    /// One key per sample, or the mismatch that says which one is short.
77    fn check(&self, what: &'static str, given: usize) -> Result<(), SamplesError> {
78        if given == self.n {
79            Ok(())
80        } else {
81            Err(SamplesError::Mismatch {
82                what,
83                given,
84                n: self.n,
85            })
86        }
87    }
88}
89
90/// Why that is not one key per sample.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum SamplesError {
93    /// As many keys as samples, and there were not.
94    Mismatch {
95        /// `classes` or `groups`.
96        what: &'static str,
97        /// How many arrived.
98        given: usize,
99        /// How many there are.
100        n: usize,
101    },
102}
103
104impl fmt::Display for SamplesError {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            Self::Mismatch { what, given, n } => write!(
108                f,
109                "{given} {what} for {n} samples: there has to be exactly one per sample, \
110                 in the same order"
111            ),
112        }
113    }
114}
115
116impl std::error::Error for SamplesError {}