pub fn seasonal_decompose(
time_series_values: &[f64],
seasonal_period_length: usize,
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let n = time_series_values.len();
if n == 0 || seasonal_period_length == 0 || seasonal_period_length > n {
return (vec![], vec![], vec![]);
}
let mut trend = vec![0.0; n];
let half = seasonal_period_length / 2;
for (i, trend_item) in trend.iter_mut().enumerate() {
let start = i.saturating_sub(half);
let end = usize::min(n, i + half + 1);
let window = &time_series_values[start..end];
*trend_item = window.iter().sum::<f64>() / window.len() as f64;
}
let detrended: Vec<f64> = time_series_values
.iter()
.zip(trend.iter())
.map(|(x, t)| x - t)
.collect();
let mut seasonal = vec![0.0; n];
let mut seasonal_means = vec![0.0; seasonal_period_length];
let mut counts = vec![0usize; seasonal_period_length];
for (i, detrended_item) in detrended.iter().enumerate() {
let idx = i % seasonal_period_length;
seasonal_means[idx] += detrended_item;
counts[idx] += 1;
}
for i in 0..seasonal_period_length {
if counts[i] > 0 {
seasonal_means[i] /= counts[i] as f64;
}
}
for i in 0..n {
seasonal[i] = seasonal_means[i % seasonal_period_length];
}
let residual: Vec<f64> = time_series_values
.iter()
.zip(trend.iter())
.zip(seasonal.iter())
.map(|((x, t), s)| x - t - s)
.collect();
(trend, seasonal, residual)
}
pub fn loess_smooth(values: &[f64], span: usize) -> Vec<f64> {
let n = values.len();
let mut result = vec![0.0; n];
for i in 0..n {
let start = i.saturating_sub(span);
let end = usize::min(n, i + span + 1);
let mut weighted_sum = 0.0;
let mut weight_total = 0.0;
for (j, j_value) in values.iter().enumerate().take(end).skip(start) {
let dist = (i as isize - j as isize).abs() as f64;
let w = (1.0 - (dist / span as f64).powi(3)).powi(3).max(0.0);
weighted_sum += w * j_value;
weight_total += w;
}
result[i] = if weight_total > 0.0 {
weighted_sum / weight_total
} else {
values[i]
};
}
result
}
pub fn seasonal_trend_decomposition_using_loess(
time_series_values: &[f64],
seasonal_period_length: usize,
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let n = time_series_values.len();
if n == 0 || seasonal_period_length == 0 || seasonal_period_length > n {
return (vec![], vec![], vec![]);
}
let trend = loess_smooth(time_series_values, seasonal_period_length);
let detrended: Vec<f64> = time_series_values
.iter()
.zip(trend.iter())
.map(|(x, t)| x - t)
.collect();
let mut seasonal = vec![0.0; n];
let mut seasonal_means = vec![0.0; seasonal_period_length];
let mut counts = vec![0usize; seasonal_period_length];
for (i, detrended_item) in detrended.iter().enumerate() {
let idx = i % seasonal_period_length;
seasonal_means[idx] += detrended_item;
counts[idx] += 1;
}
for i in 0..seasonal_period_length {
if counts[i] > 0 {
seasonal_means[i] /= counts[i] as f64;
}
}
for i in 0..n {
seasonal[i] = seasonal_means[i % seasonal_period_length];
}
let residual: Vec<f64> = time_series_values
.iter()
.zip(trend.iter())
.zip(seasonal.iter())
.map(|((x, t), s)| x - t - s)
.collect();
(trend, seasonal, residual)
}