use crate::TimeSeries;
use torsh_tensor::Tensor;
pub fn diff(series: &TimeSeries, order: usize) -> TimeSeries {
if order == 0 {
return TimeSeries::new(series.values.clone());
}
let mut data = series.values.to_vec().unwrap_or_default();
for _ in 0..order {
if data.len() <= 1 {
break;
}
let mut diff_data = Vec::with_capacity(data.len() - 1);
for i in 1..data.len() {
diff_data.push(data[i] - data[i - 1]);
}
data = diff_data;
}
let n = data.len();
let tensor = if n > 0 {
Tensor::from_vec(data, &[n]).expect("tensor creation should succeed")
} else {
Tensor::from_vec(vec![0.0f32], &[1]).expect("tensor creation should succeed")
};
TimeSeries::new(tensor)
}
pub fn detrend(series: &TimeSeries, method: &str) -> TimeSeries {
let data = series.values.to_vec().unwrap_or_default();
if data.is_empty() {
return TimeSeries::new(series.values.clone());
}
let n = data.len();
match method {
"mean" => {
let mean = data.iter().sum::<f32>() / n as f32;
let detrended: Vec<f32> = data.iter().map(|&x| x - mean).collect();
let tensor = Tensor::from_vec(detrended, &[n]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
_ => {
let t: Vec<f64> = (0..n).map(|i| i as f64).collect();
let y: Vec<f64> = data.iter().map(|&x| x as f64).collect();
let sum_t: f64 = t.iter().sum();
let sum_y: f64 = y.iter().sum();
let sum_t_squared: f64 = t.iter().map(|&x| x * x).sum();
let sum_t_y: f64 = t.iter().zip(y.iter()).map(|(&ti, &yi)| ti * yi).sum();
let n_f64 = n as f64;
let denominator = n_f64 * sum_t_squared - sum_t * sum_t;
let (slope, intercept) = if denominator.abs() > 1e-10 {
let slope = (n_f64 * sum_t_y - sum_t * sum_y) / denominator;
let intercept = (sum_y - slope * sum_t) / n_f64;
(slope, intercept)
} else {
(0.0, sum_y / n_f64)
};
let detrended: Vec<f32> = t
.iter()
.zip(y.iter())
.map(|(&ti, &yi)| {
let fitted = slope * ti + intercept;
(yi - fitted) as f32
})
.collect();
let tensor = Tensor::from_vec(detrended, &[n]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
}
}
pub fn normalize(series: &TimeSeries) -> TimeSeries {
standard_scale(series)
}
pub fn moving_average(series: &TimeSeries, window: usize) -> TimeSeries {
if window == 0 {
return TimeSeries::new(series.values.clone());
}
let data = series.values.to_vec().unwrap_or_default();
if data.is_empty() {
return TimeSeries::new(series.values.clone());
}
let n = data.len();
let mut result = Vec::with_capacity(n);
for i in 0..n {
let start = if i + 1 >= window { i + 1 - window } else { 0 };
let window_size = i - start + 1;
let sum: f32 = data[start..=i].iter().sum();
let mean = sum / window_size as f32;
result.push(mean);
}
let tensor = Tensor::from_vec(result, &[n]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
pub fn ema(series: &TimeSeries, alpha: f64) -> TimeSeries {
let mut result = vec![0.0f32; series.len()];
let values = series.values.to_vec().expect("conversion should succeed");
result[0] = values[0];
for i in 1..values.len() {
result[i] = (alpha as f32) * values[i] + ((1.0 - alpha) as f32) * result[i - 1];
}
let tensor = Tensor::from_vec(result, &[series.len()]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
pub fn box_cox(series: &TimeSeries, lambda: f32) -> TimeSeries {
let data = series.values.to_vec().unwrap_or_default();
let transformed: Vec<f32> = data
.iter()
.map(|&x| {
if x <= 0.0 {
if lambda.abs() < 1e-6 {
(1e-10f32).ln()
} else {
((1e-10f32).powf(lambda) - 1.0) / lambda
}
} else if lambda.abs() < 1e-6 {
x.ln()
} else {
(x.powf(lambda) - 1.0) / lambda
}
})
.collect();
let tensor =
Tensor::from_vec(transformed, &[series.len()]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
pub fn inv_box_cox(series: &TimeSeries, lambda: f32) -> TimeSeries {
let data = series.values.to_vec().unwrap_or_default();
let inv_transformed: Vec<f32> = data
.iter()
.map(|&y| {
if lambda.abs() < 1e-6 {
y.exp()
} else {
let base = lambda * y + 1.0;
if base > 0.0 {
base.powf(1.0 / lambda)
} else {
1e-10
}
}
})
.collect();
let tensor =
Tensor::from_vec(inv_transformed, &[series.len()]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
pub fn standard_scale(series: &TimeSeries) -> TimeSeries {
let data = series.values.to_vec().unwrap_or_default();
if data.is_empty() {
return TimeSeries::new(series.values.clone());
}
let mean = data.iter().sum::<f32>() / data.len() as f32;
let variance = data.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / data.len() as f32;
let std = variance.sqrt();
if std < 1e-10 {
let zeros_vec = vec![0.0f32; data.len()];
let tensor =
Tensor::from_vec(zeros_vec, &[series.len()]).expect("tensor creation should succeed");
return TimeSeries::new(tensor);
}
let standardized: Vec<f32> = data.iter().map(|&x| (x - mean) / std).collect();
let tensor =
Tensor::from_vec(standardized, &[series.len()]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
pub fn min_max_scale(series: &TimeSeries) -> TimeSeries {
let data = series.values.to_vec().unwrap_or_default();
if data.is_empty() {
return TimeSeries::new(series.values.clone());
}
let min_val = data.iter().cloned().fold(f32::INFINITY, f32::min);
let max_val = data.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let range = max_val - min_val;
if range < 1e-10 {
let zeros_vec = vec![0.0f32; data.len()];
let tensor =
Tensor::from_vec(zeros_vec, &[series.len()]).expect("tensor creation should succeed");
return TimeSeries::new(tensor);
}
let scaled: Vec<f32> = data.iter().map(|&x| (x - min_val) / range).collect();
let tensor = Tensor::from_vec(scaled, &[series.len()]).expect("tensor creation should succeed");
TimeSeries::new(tensor)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_series() -> TimeSeries {
let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
let tensor = Tensor::from_vec(data, &[5]).expect("Tensor should succeed");
TimeSeries::new(tensor)
}
#[test]
fn test_diff() {
let series = create_test_series();
let diffed = diff(&series, 1);
assert_eq!(diffed.len(), series.len() - 1);
let result = diffed
.values
.to_vec()
.expect("tensor to_vec conversion should succeed");
assert_eq!(result.len(), 4);
for &val in &result {
assert!((val - 1.0).abs() < 1e-6, "First difference should be 1.0");
}
let diffed2 = diff(&series, 2);
assert_eq!(diffed2.len(), series.len() - 2);
let result2 = diffed2
.values
.to_vec()
.expect("tensor to_vec conversion should succeed");
assert_eq!(result2.len(), 3);
for &val in &result2 {
assert!(val.abs() < 1e-6, "Second difference should be 0.0");
}
let diffed0 = diff(&series, 0);
assert_eq!(diffed0.len(), series.len());
}
#[test]
fn test_detrend() {
let series = create_test_series();
let detrended = detrend(&series, "linear");
assert_eq!(detrended.len(), series.len());
let result = detrended
.values
.to_vec()
.expect("tensor to_vec conversion should succeed");
for &val in &result {
assert!(val.abs() < 1e-5, "Detrended value {} should be near 0", val);
}
let mean_detrended = detrend(&series, "mean");
let mean_result = mean_detrended
.values
.to_vec()
.expect("tensor to_vec conversion should succeed");
let mean_after: f32 = mean_result.iter().sum::<f32>() / mean_result.len() as f32;
assert!(
mean_after.abs() < 1e-5,
"Mean after detrending should be ~0"
);
}
#[test]
fn test_normalize() {
let series = create_test_series(); let normalized = normalize(&series);
assert_eq!(normalized.len(), series.len());
let result = normalized
.values
.to_vec()
.expect("tensor to_vec conversion should succeed");
let mean = result.iter().sum::<f32>() / result.len() as f32;
assert!(mean.abs() < 1e-5, "Mean after normalization should be ~0");
let variance =
result.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / result.len() as f32;
let std = variance.sqrt();
assert!(
(std - 1.0).abs() < 1e-5,
"Std after normalization should be ~1"
);
}
#[test]
fn test_ema() {
let series = create_test_series();
let smoothed = ema(&series, 0.3);
assert_eq!(smoothed.len(), series.len());
}
#[test]
fn test_moving_average() {
let series = create_test_series(); let smoothed = moving_average(&series, 3);
assert_eq!(smoothed.len(), series.len());
let result = smoothed
.values
.to_vec()
.expect("tensor to_vec conversion should succeed");
assert!((result[0] - 1.0).abs() < 1e-6);
assert!((result[1] - 1.5).abs() < 1e-6);
assert!((result[2] - 2.0).abs() < 1e-6);
assert!((result[3] - 3.0).abs() < 1e-6);
assert!((result[4] - 4.0).abs() < 1e-6);
}
}