Skip to main content

somatize_study/partition/
time_series.rs

1//! `k` growing prefixes, so nothing is ever trained on its own future.
2
3use super::{Fold, Partition, PartitionError};
4use crate::Samples;
5use std::fmt;
6
7/// The one scheme that is deliberately **not** a partition: the first block has
8/// nothing before it to learn from, so it only ever trains and is never held
9/// out. Every other scheme here holds out each sample exactly once.
10///
11/// `gap` drops that many samples between the two sides, which is what purged
12/// and embargoed cross-validation are — a parameter, not a scheme. It uses
13/// neither the classes nor the groups, and unlike the rest `k = 1` is
14/// meaningful: a plain holdout of the tail.
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct TimeSeries {
17    /// How many splits.
18    pub k: usize,
19    /// How many samples to drop between training and test.
20    pub gap: usize,
21}
22
23impl TimeSeries {
24    /// The folds, oldest first.
25    pub fn folds(&self, samples: &Samples) -> Result<Vec<Fold>, PartitionError> {
26        let n = samples.n();
27        if self.k == 0 {
28            return Err(PartitionError::TooFewFolds { k: self.k });
29        }
30        // One more part than splits: the first one only ever trains.
31        let size = n / (self.k + 1);
32        if size == 0 {
33            return Err(PartitionError::MoreFoldsThanSamples { k: self.k, n });
34        }
35        (0..self.k)
36            .map(|i| {
37                let start = n - (self.k - i) * size;
38                if start <= self.gap {
39                    return Err(PartitionError::GapTooLarge {
40                        gap: self.gap,
41                        k: self.k,
42                    });
43                }
44                Ok(Fold {
45                    train: (0..start - self.gap).collect(),
46                    test: (start..start + size).collect(),
47                })
48            })
49            .collect()
50    }
51}
52
53impl fmt::Display for TimeSeries {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self.gap {
56            0 => write!(f, "timeseries:{}", self.k),
57            gap => write!(f, "timeseries:{}:gap:{gap}", self.k),
58        }
59    }
60}
61
62impl From<TimeSeries> for Partition {
63    fn from(cut: TimeSeries) -> Self {
64        Self::TimeSeries(cut)
65    }
66}