Skip to main content

fdars_core/detrend/
mod.rs

1//! Detrending and decomposition functions for non-stationary functional data.
2//!
3//! This module provides methods for removing trends from functional data
4//! to enable more accurate seasonal analysis. It includes:
5//! - Linear detrending (least squares)
6//! - Polynomial detrending (QR decomposition)
7//! - Differencing (first and second order)
8//! - LOESS detrending (local polynomial regression)
9//! - Spline detrending (P-splines)
10//! - Automatic method selection via AIC
11
12use crate::matrix::FdMatrix;
13use std::borrow::Cow;
14
15pub mod auto;
16pub mod decompose;
17pub mod diff;
18pub mod linear;
19pub mod loess;
20pub mod polynomial;
21pub mod stl;
22
23#[cfg(test)]
24mod tests;
25
26// ---------------------------------------------------------------------------
27// Shared types
28// ---------------------------------------------------------------------------
29
30/// Result of detrending operation.
31#[derive(Debug, Clone)]
32#[non_exhaustive]
33pub struct TrendResult {
34    /// Estimated trend values (n x m)
35    pub trend: FdMatrix,
36    /// Detrended data (n x m)
37    pub detrended: FdMatrix,
38    /// Method used for detrending
39    pub method: Cow<'static, str>,
40    /// Polynomial coefficients (for polynomial methods, per sample)
41    /// For n samples with polynomial degree d: n x (d+1)
42    pub coefficients: Option<FdMatrix>,
43    /// Residual sum of squares for each sample
44    pub rss: Vec<f64>,
45    /// Number of parameters (for AIC calculation)
46    pub n_params: usize,
47}
48
49impl TrendResult {
50    /// Construct a no-op TrendResult (zero trend, data copied to detrended).
51    pub(super) fn empty(
52        data: &FdMatrix,
53        n: usize,
54        m: usize,
55        method: Cow<'static, str>,
56        n_params: usize,
57    ) -> Self {
58        TrendResult {
59            trend: FdMatrix::zeros(n, m),
60            detrended: FdMatrix::from_slice(data.as_slice(), n, m)
61                .unwrap_or_else(|_| FdMatrix::zeros(n, m)),
62            method,
63            coefficients: None,
64            rss: vec![0.0; n],
65            n_params,
66        }
67    }
68}
69
70/// Result of seasonal decomposition.
71#[derive(Debug, Clone)]
72#[non_exhaustive]
73pub struct DecomposeResult {
74    /// Trend component (n x m)
75    pub trend: FdMatrix,
76    /// Seasonal component (n x m)
77    pub seasonal: FdMatrix,
78    /// Remainder/residual component (n x m)
79    pub remainder: FdMatrix,
80    /// Period used for decomposition
81    pub period: f64,
82    /// Decomposition method ("additive" or "multiplicative")
83    pub method: Cow<'static, str>,
84}
85
86// ---------------------------------------------------------------------------
87// Shared helpers
88// ---------------------------------------------------------------------------
89
90/// Reassemble per-curve (trend, detrended, coefficients, rss) results into FdMatrix outputs.
91pub(super) fn reassemble_polynomial_results(
92    results: Vec<(Vec<f64>, Vec<f64>, Vec<f64>, f64)>,
93    n: usize,
94    m: usize,
95    n_coef: usize,
96) -> (FdMatrix, FdMatrix, FdMatrix, Vec<f64>) {
97    let mut trend = FdMatrix::zeros(n, m);
98    let mut detrended = FdMatrix::zeros(n, m);
99    let mut coefficients = FdMatrix::zeros(n, n_coef);
100    let mut rss = vec![0.0; n];
101    for (i, (t, d, coefs, r)) in results.into_iter().enumerate() {
102        for j in 0..m {
103            trend[(i, j)] = t[j];
104            detrended[(i, j)] = d[j];
105        }
106        for k in 0..n_coef {
107            coefficients[(i, k)] = coefs[k];
108        }
109        rss[i] = r;
110    }
111    (trend, detrended, coefficients, rss)
112}
113
114/// Reassemble per-curve (trend, detrended, rss) results into FdMatrix outputs.
115pub(super) fn reassemble_trend_results(
116    results: Vec<(Vec<f64>, Vec<f64>, f64)>,
117    n: usize,
118    m: usize,
119) -> (FdMatrix, FdMatrix, Vec<f64>) {
120    let mut trend = FdMatrix::zeros(n, m);
121    let mut detrended = FdMatrix::zeros(n, m);
122    let mut rss = vec![0.0; n];
123    for (i, (t, d, r)) in results.into_iter().enumerate() {
124        for j in 0..m {
125            trend[(i, j)] = t[j];
126            detrended[(i, j)] = d[j];
127        }
128        rss[i] = r;
129    }
130    (trend, detrended, rss)
131}
132
133// ---------------------------------------------------------------------------
134// Re-exports -- preserves the external API
135// ---------------------------------------------------------------------------
136
137pub use auto::auto_detrend;
138pub use decompose::{decompose_additive, decompose_multiplicative};
139pub use diff::detrend_diff;
140pub use linear::detrend_linear;
141pub use loess::detrend_loess;
142pub use polynomial::detrend_polynomial;
143pub use stl::{stl_decompose, stl_decompose_with_config, stl_fdata, StlConfig, StlResult};