sklears_preprocessing/temporal/
trend.rs1use scirs2_core::ndarray::{s, Array1};
7use sklears_core::{
8 error::Result,
9 traits::{Fit, Trained, Transform, Untrained},
10 types::Float,
11};
12use std::marker::PhantomData;
13
14#[derive(Debug, Clone)]
16pub struct TrendDetectorConfig {
17 pub method: TrendMethod,
19 pub window_size: usize,
21 pub polynomial_degree: usize,
23}
24
25#[derive(Debug, Clone, Copy)]
27pub enum TrendMethod {
28 Linear,
30 Polynomial,
32 LocalLinear,
34 MannKendall,
36}
37
38impl Default for TrendDetectorConfig {
39 fn default() -> Self {
40 Self {
41 method: TrendMethod::Linear,
42 window_size: 10,
43 polynomial_degree: 2,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct TrendDetector<S> {
51 config: TrendDetectorConfig,
52 _phantom: PhantomData<S>,
53}
54
55impl TrendDetector<Untrained> {
56 pub fn new() -> Self {
58 Self {
59 config: TrendDetectorConfig::default(),
60 _phantom: PhantomData,
61 }
62 }
63
64 pub fn method(mut self, method: TrendMethod) -> Self {
66 self.config.method = method;
67 self
68 }
69
70 pub fn window_size(mut self, window_size: usize) -> Self {
72 self.config.window_size = window_size;
73 self
74 }
75
76 pub fn polynomial_degree(mut self, polynomial_degree: usize) -> Self {
78 self.config.polynomial_degree = polynomial_degree;
79 self
80 }
81}
82
83impl TrendDetector<Trained> {
84 fn calculate_linear_trend(&self, data: &Array1<Float>) -> Float {
86 let n = data.len() as Float;
87 let x_mean = (n - 1.0) / 2.0;
88 let y_mean = data.mean().unwrap_or(0.0);
89
90 let mut numerator = 0.0;
91 let mut denominator = 0.0;
92
93 for (i, &y) in data.iter().enumerate() {
94 let x = i as Float;
95 numerator += (x - x_mean) * (y - y_mean);
96 denominator += (x - x_mean).powi(2);
97 }
98
99 if denominator.abs() > 1e-10 {
100 numerator / denominator
101 } else {
102 0.0
103 }
104 }
105
106 fn calculate_mann_kendall(&self, data: &Array1<Float>) -> Float {
108 let n = data.len();
109 let mut s = 0i32;
110
111 for i in 0..n {
112 for j in (i + 1)..n {
113 if data[j] > data[i] {
114 s += 1;
115 } else if data[j] < data[i] {
116 s -= 1;
117 }
118 }
119 }
120
121 let max_s = (n * (n - 1) / 2) as i32;
123 if max_s > 0 {
124 s as Float / max_s as Float
125 } else {
126 0.0
127 }
128 }
129
130 fn calculate_local_trends(&self, data: &Array1<Float>) -> Array1<Float> {
132 let n = data.len();
133 let window_size = self.config.window_size.min(n);
134 let mut trends = Array1::<Float>::zeros(n);
135
136 for i in 0..n {
137 let start = if i >= window_size / 2 {
138 (i - window_size / 2).max(0)
139 } else {
140 0
141 };
142 let end = (start + window_size).min(n);
143 let window = data.slice(s![start..end]);
144 trends[i] = self.calculate_linear_trend(&window.to_owned());
145 }
146
147 trends
148 }
149}
150
151impl Default for TrendDetector<Untrained> {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl Fit<Array1<Float>, ()> for TrendDetector<Untrained> {
158 type Fitted = TrendDetector<Trained>;
159
160 fn fit(self, _x: &Array1<Float>, _y: &()) -> Result<Self::Fitted> {
161 Ok(TrendDetector {
162 config: self.config,
163 _phantom: PhantomData,
164 })
165 }
166}
167
168impl Transform<Array1<Float>, Array1<Float>> for TrendDetector<Trained> {
169 fn transform(&self, x: &Array1<Float>) -> Result<Array1<Float>> {
170 match self.config.method {
171 TrendMethod::Linear => {
172 let slope = self.calculate_linear_trend(x);
173 Ok(Array1::from_elem(x.len(), slope))
174 }
175 TrendMethod::MannKendall => {
176 let mk_stat = self.calculate_mann_kendall(x);
177 Ok(Array1::from_elem(x.len(), mk_stat))
178 }
179 TrendMethod::LocalLinear => Ok(self.calculate_local_trends(x)),
180 TrendMethod::Polynomial => {
181 let slope = self.calculate_linear_trend(x);
183 Ok(Array1::from_elem(x.len(), slope))
184 }
185 }
186 }
187}