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 {}