u_analytics/error.rs
1//! Refusals shared by the functions of more than one module.
2
3/// Why a time-series statistic was refused: [`crate::correlation::acf`],
4/// [`crate::correlation::pacf`], [`crate::testing::adf_test`] and
5/// [`crate::testing::kpss_test`].
6///
7/// The data is checked before the options, and the options before any
8/// computation: a refusal of an option never hides a refusal of the data,
9/// and [`TimeSeriesError::NoVariation`] / [`TimeSeriesError::DegenerateRegression`]
10/// only follow options that were in range.
11#[derive(Debug, Clone, Copy, PartialEq)]
12#[non_exhaustive]
13pub enum TimeSeriesError {
14 /// The series holds a NaN or an infinity.
15 ValueNotFinite {
16 /// Position of the first such value.
17 index: usize,
18 },
19 /// Fewer observations than the statistic is defined for.
20 TooFewObservations {
21 /// The fewest it takes.
22 needed: usize,
23 /// How many were given.
24 got: usize,
25 },
26 /// An option outside its domain, which depends on the number of
27 /// observations: `min` and `max` are both inclusive.
28 OptionOutOfRange {
29 /// The option's name, spelled as the wire formats spell it
30 /// (`max_lag`, `max_lags`, `lags`).
31 option: &'static str,
32 /// The value given (a count as a float).
33 got: f64,
34 /// Inclusive lower bound.
35 min: f64,
36 /// Inclusive upper bound.
37 max: f64,
38 },
39 /// The series does not vary (about its level, or its trend, for the KPSS
40 /// test) beyond the rounding of its own size, so the statistic divides
41 /// by zero.
42 NoVariation,
43 /// The ADF test regression is singular, or fits the differences exactly
44 /// (a deterministic series), so the t-ratio is undefined.
45 DegenerateRegression,
46}
47
48impl core::fmt::Display for TimeSeriesError {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 match self {
51 TimeSeriesError::ValueNotFinite { index } => {
52 write!(f, "the value at index {index} is not finite")
53 }
54 TimeSeriesError::TooFewObservations { needed, got } => {
55 write!(f, "at least {needed} values are needed, got {got}")
56 }
57 TimeSeriesError::OptionOutOfRange {
58 option,
59 got,
60 min,
61 max,
62 } => write!(f, "`{option}` must be in [{min}, {max}] (got {got})"),
63 TimeSeriesError::NoVariation => {
64 write!(f, "the series does not vary, so the statistic is undefined")
65 }
66 TimeSeriesError::DegenerateRegression => write!(
67 f,
68 "the test regression is singular, or an exact fit of the differences \
69 (a deterministic series), so the t-ratio is undefined"
70 ),
71 }
72 }
73}
74
75impl std::error::Error for TimeSeriesError {}
76
77/// The series' own checks, in the order every time-series statistic applies
78/// them: a non-finite value, then too few values.
79pub(crate) fn check_series(data: &[f64], needed: usize) -> Result<(), TimeSeriesError> {
80 if let Some(index) = data.iter().position(|v| !v.is_finite()) {
81 return Err(TimeSeriesError::ValueNotFinite { index });
82 }
83 if data.len() < needed {
84 return Err(TimeSeriesError::TooFewObservations {
85 needed,
86 got: data.len(),
87 });
88 }
89 Ok(())
90}
91
92/// Refuses a count option outside `[min, max]`.
93pub(crate) fn check_option(
94 option: &'static str,
95 value: usize,
96 min: usize,
97 max: usize,
98) -> Result<usize, TimeSeriesError> {
99 if (min..=max).contains(&value) {
100 Ok(value)
101 } else {
102 Err(TimeSeriesError::OptionOutOfRange {
103 option,
104 got: value as f64,
105 min: min as f64,
106 max: max as f64,
107 })
108 }
109}