#![allow(clippy::result_large_err)]
use chrono::NaiveDate;
use pandrs::temporal::{date_range, Frequency};
use pandrs::NA;
fn parse_date(date_str: &str) -> NaiveDate {
NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap()
}
#[test]
fn test_rolling_window_basic_operations() {
let dates = date_range(
parse_date("2023-01-01"),
parse_date("2023-01-07"),
Frequency::Daily,
true,
)
.unwrap();
let values: Vec<_> = (1..=7).map(|i| NA::Value(i as f64)).collect();
let ts = pandrs::temporal::TimeSeries::new(values.to_vec(), dates, None).unwrap();
let rolling_mean = ts.rolling(3).unwrap().mean().unwrap();
assert!(rolling_mean.values()[0].is_na());
assert!(rolling_mean.values()[1].is_na());
assert_eq!(rolling_mean.values()[2], NA::Value((1.0 + 2.0 + 3.0) / 3.0));
assert_eq!(rolling_mean.values()[3], NA::Value((2.0 + 3.0 + 4.0) / 3.0));
assert_eq!(rolling_mean.values()[4], NA::Value((3.0 + 4.0 + 5.0) / 3.0));
assert_eq!(rolling_mean.values()[5], NA::Value((4.0 + 5.0 + 6.0) / 3.0));
assert_eq!(rolling_mean.values()[6], NA::Value((5.0 + 6.0 + 7.0) / 3.0));
let rolling_sum = ts.rolling(3).unwrap().sum().unwrap();
assert!(rolling_sum.values()[0].is_na());
assert!(rolling_sum.values()[1].is_na());
assert_eq!(rolling_sum.values()[2], NA::Value(1.0 + 2.0 + 3.0));
assert_eq!(rolling_sum.values()[3], NA::Value(2.0 + 3.0 + 4.0));
let rolling_max = ts.rolling(3).unwrap().max().unwrap();
assert!(rolling_max.values()[0].is_na());
assert!(rolling_max.values()[1].is_na());
assert_eq!(rolling_max.values()[2], NA::Value(3.0));
assert_eq!(rolling_max.values()[3], NA::Value(4.0));
assert_eq!(rolling_max.values()[4], NA::Value(5.0));
let rolling_min = ts.rolling(3).unwrap().min().unwrap();
assert!(rolling_min.values()[0].is_na());
assert!(rolling_min.values()[1].is_na());
assert_eq!(rolling_min.values()[2], NA::Value(1.0));
assert_eq!(rolling_min.values()[3], NA::Value(2.0));
assert_eq!(rolling_min.values()[4], NA::Value(3.0));
let rolling_std = ts.rolling(3).unwrap().std(1).unwrap();
assert!(rolling_std.values()[0].is_na());
assert!(rolling_std.values()[1].is_na());
let std_1_2_3 = ((f64::powi(1.0 - 2.0, 2) + f64::powi(2.0 - 2.0, 2) + f64::powi(3.0 - 2.0, 2))
/ 2.0)
.sqrt();
let actual_std = match rolling_std.values()[2] {
NA::Value(v) => v,
NA::NA => panic!("Expected a value, got NA"),
};
assert!((actual_std - std_1_2_3).abs() < 1e-10);
}
#[test]
fn test_expanding_window_operations() {
let dates = date_range(
parse_date("2023-01-01"),
parse_date("2023-01-05"),
Frequency::Daily,
true,
)
.unwrap();
let values = [
NA::Value(10.0),
NA::Value(20.0),
NA::Value(30.0),
NA::Value(40.0),
NA::Value(50.0),
];
let ts = pandrs::temporal::TimeSeries::new(values.to_vec(), dates, None).unwrap();
let expanding_mean = ts.expanding(2).unwrap().mean().unwrap();
assert!(expanding_mean.values()[0].is_na());
assert_eq!(expanding_mean.values()[1], NA::Value((10.0 + 20.0) / 2.0)); assert_eq!(
expanding_mean.values()[2],
NA::Value((10.0 + 20.0 + 30.0) / 3.0)
); assert_eq!(
expanding_mean.values()[3],
NA::Value((10.0 + 20.0 + 30.0 + 40.0) / 4.0)
); assert_eq!(
expanding_mean.values()[4],
NA::Value((10.0 + 20.0 + 30.0 + 40.0 + 50.0) / 5.0)
);
let expanding_sum = ts.expanding(2).unwrap().sum().unwrap();
assert!(expanding_sum.values()[0].is_na());
assert_eq!(expanding_sum.values()[1], NA::Value(10.0 + 20.0));
assert_eq!(expanding_sum.values()[2], NA::Value(10.0 + 20.0 + 30.0));
assert_eq!(
expanding_sum.values()[3],
NA::Value(10.0 + 20.0 + 30.0 + 40.0)
);
assert_eq!(
expanding_sum.values()[4],
NA::Value(10.0 + 20.0 + 30.0 + 40.0 + 50.0)
);
}
#[test]
fn test_ewm_operations() {
let dates = date_range(
parse_date("2023-01-01"),
parse_date("2023-01-05"),
Frequency::Daily,
true,
)
.unwrap();
let values = [
NA::Value(10.0),
NA::Value(20.0),
NA::Value(30.0),
NA::Value(40.0),
NA::Value(50.0),
];
let ts = pandrs::temporal::TimeSeries::new(values.to_vec(), dates, None).unwrap();
let ewm_mean = ts.ewm(None, Some(0.5), false).unwrap().mean().unwrap();
assert_eq!(ewm_mean.values()[0], NA::Value(10.0));
assert_eq!(ewm_mean.values()[1], NA::Value(15.0));
assert_eq!(ewm_mean.values()[2], NA::Value(22.5));
assert_eq!(ewm_mean.values()[3], NA::Value(31.25));
assert_eq!(ewm_mean.values()[4], NA::Value(40.625));
let ewm_span = ts.ewm(Some(5), None, false).unwrap().mean().unwrap();
assert_eq!(ewm_span.values()[0], NA::Value(10.0));
let alpha = 1.0 / 3.0;
let expected_y2 = alpha * 20.0 + (1.0 - alpha) * 10.0;
let expected_y3 = alpha * 30.0 + (1.0 - alpha) * expected_y2;
let expected_y4 = alpha * 40.0 + (1.0 - alpha) * expected_y3;
let _expected_y5 = alpha * 50.0 + (1.0 - alpha) * expected_y4;
let actual_y2 = match ewm_span.values()[1] {
NA::Value(v) => v,
NA::NA => panic!("Expected a value, got NA"),
};
assert!((actual_y2 - expected_y2).abs() < 1e-10);
let actual_y3 = match ewm_span.values()[2] {
NA::Value(v) => v,
NA::NA => panic!("Expected a value, got NA"),
};
assert!((actual_y3 - expected_y3).abs() < 1e-10);
}
#[test]
fn test_window_with_na_values() {
let dates = date_range(
parse_date("2023-01-01"),
parse_date("2023-01-07"),
Frequency::Daily,
true,
)
.unwrap();
let values = [
NA::Value(10.0),
NA::NA,
NA::Value(30.0),
NA::Value(40.0),
NA::NA,
NA::Value(60.0),
NA::Value(70.0),
];
let ts = pandrs::temporal::TimeSeries::new(values.to_vec(), dates, None).unwrap();
let rolling_mean = ts.rolling(3).unwrap().mean().unwrap();
assert!(rolling_mean.values()[0].is_na());
assert!(rolling_mean.values()[1].is_na());
if let NA::Value(_) = rolling_mean.values()[2] {
}
if let NA::Value(_) = rolling_mean.values()[3] {
}
}
#[test]
fn test_custom_aggregate_function() {
let dates = date_range(
parse_date("2023-01-01"),
parse_date("2023-01-05"),
Frequency::Daily,
true,
)
.unwrap();
let values = [
NA::Value(10.0),
NA::Value(20.0),
NA::Value(30.0),
NA::Value(40.0),
NA::Value(50.0),
];
let ts = pandrs::temporal::TimeSeries::new(values.to_vec(), dates, None).unwrap();
let median = |values: &[f64]| -> f64 {
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mid = sorted.len() / 2;
if sorted.len().is_multiple_of(2) {
(sorted[mid - 1] + sorted[mid]) / 2.0
} else {
sorted[mid]
}
};
let rolling_median = ts.rolling(3).unwrap().aggregate(median, Some(2)).unwrap();
assert!(rolling_median.values()[0].is_na());
match rolling_median.values()[1] {
NA::Value(v) => {
assert_eq!(v, 15.0);
}
NA::NA => {
}
};
match rolling_median.values()[2] {
NA::Value(v) => {
assert_eq!(v, 20.0);
}
NA::NA => {
}
};
match rolling_median.values()[3] {
NA::Value(v) => {
assert_eq!(v, 30.0);
}
NA::NA => {
}
};
match rolling_median.values()[4] {
NA::Value(v) => {
assert_eq!(v, 40.0);
}
NA::NA => {
}
};
let percentile_75 = |values: &[f64]| -> f64 {
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let idx = (values.len() as f64 * 0.75).floor() as usize;
sorted[idx]
};
let rolling_p75 = ts
.rolling(3)
.unwrap()
.aggregate(percentile_75, Some(2))
.unwrap();
match rolling_p75.values()[1] {
NA::Value(v) => {
assert_eq!(v, 20.0);
}
NA::NA => {
}
};
match rolling_p75.values()[2] {
NA::Value(v) => {
assert_eq!(v, 30.0);
}
NA::NA => {
}
};
match rolling_p75.values()[3] {
NA::Value(v) => {
assert_eq!(v, 40.0);
}
NA::NA => {
}
};
match rolling_p75.values()[4] {
NA::Value(v) => {
assert_eq!(v, 50.0);
}
NA::NA => {
}
};
}
#[test]
fn test_window_edge_cases() {
let dates = date_range(
parse_date("2023-01-01"),
parse_date("2023-01-03"),
Frequency::Daily,
true,
)
.unwrap();
let values = [NA::Value(10.0), NA::Value(20.0), NA::Value(30.0)];
let ts = pandrs::temporal::TimeSeries::new(values.to_vec(), dates, None).unwrap();
let result = ts.rolling(4);
if result.is_err() {
assert!(result.is_err());
} else if let Ok(window) = result {
let _ = window.mean(); }
let result = ts.rolling(0);
assert!(result.is_err());
let result = ts.ewm(None, Some(0.0), false);
if let Ok(ewm) = result {
let alpha_result = ewm.with_alpha(0.0);
assert!(alpha_result.is_err());
}
let result = ts.ewm(None, Some(1.1), false);
if let Ok(ewm) = result {
let alpha_result = ewm.with_alpha(1.1);
assert!(alpha_result.is_err());
}
let empty_ts: pandrs::temporal::TimeSeries<chrono::NaiveDate> =
pandrs::temporal::TimeSeries::new(Vec::new(), Vec::new(), None).unwrap();
let result = empty_ts.rolling(1);
if let Ok(window) = result {
let rolling = window.mean();
if let Ok(result_series) = rolling {
assert_eq!(result_series.len(), 0);
}
} else {
assert!(result.is_err());
}
}