use crate::core::error::{Error, Result};
use crate::series::base::Series;
use std::fmt::Debug;
fn series_values_as_f64_opt<T>(series: &Series<T>) -> Vec<Option<f64>>
where
T: Debug + Clone + Into<f64> + Copy,
{
series
.values()
.iter()
.map(|&v| {
let f: f64 = v.into();
if f.is_nan() {
None
} else {
Some(f)
}
})
.collect()
}
#[derive(Debug, Clone)]
pub struct Rolling<T>
where
T: Debug + Clone,
{
series: Series<T>,
window_size: usize,
min_periods: Option<usize>,
center: bool,
closed: WindowClosed,
}
#[derive(Debug, Clone)]
pub struct Expanding<T>
where
T: Debug + Clone,
{
series: Series<T>,
min_periods: usize,
}
#[derive(Debug, Clone)]
pub struct EWM<T>
where
T: Debug + Clone,
{
series: Series<T>,
alpha: Option<f64>,
span: Option<usize>,
halflife: Option<f64>,
adjust: bool,
ignore_na: bool,
min_periods: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowClosed {
Both,
Left,
Right,
Neither,
}
impl Default for WindowClosed {
fn default() -> Self {
WindowClosed::Right
}
}
pub trait WindowOps<T>
where
T: Debug + Clone,
{
fn mean(&self) -> Result<Series<f64>>;
fn sum(&self) -> Result<Series<f64>>;
fn std(&self, ddof: usize) -> Result<Series<f64>>;
fn var(&self, ddof: usize) -> Result<Series<f64>>;
fn min(&self) -> Result<Series<f64>>;
fn max(&self) -> Result<Series<f64>>;
fn count(&self) -> Result<Series<usize>>;
fn median(&self) -> Result<Series<f64>>;
fn quantile(&self, q: f64) -> Result<Series<f64>>;
fn apply<F, R>(&self, func: F) -> Result<Series<Option<R>>>
where
F: Fn(&[f64]) -> R + Copy,
R: Debug + Clone;
}
impl<T> Rolling<T>
where
T: Debug + Clone,
{
pub fn new(series: Series<T>, window_size: usize) -> Result<Self> {
if window_size == 0 {
return Err(Error::InvalidValue(
"Window size must be greater than 0".to_string(),
));
}
Ok(Self {
series,
window_size,
min_periods: None,
center: false,
closed: WindowClosed::default(),
})
}
pub fn min_periods(mut self, min_periods: usize) -> Self {
self.min_periods = Some(min_periods);
self
}
pub fn center(mut self, center: bool) -> Self {
self.center = center;
self
}
pub fn closed(mut self, closed: WindowClosed) -> Self {
self.closed = closed;
self
}
fn effective_min_periods(&self) -> usize {
self.min_periods.unwrap_or(self.window_size)
}
fn values_as_f64(&self) -> Result<Vec<Option<f64>>>
where
T: Into<f64> + Copy,
{
Ok(series_values_as_f64_opt(&self.series))
}
fn apply_window_op<F, R>(&self, mut func: F) -> Result<Series<Option<R>>>
where
T: Into<f64> + Copy,
F: FnMut(&[f64]) -> R,
R: Debug + Clone,
{
let values = self.values_as_f64()?;
let n = values.len();
let min_periods = self.effective_min_periods();
let mut trailing: Vec<Option<R>> = Vec::with_capacity(n);
for i in 0..n {
let end1 = (i + 1) as i64;
let start1 = end1 - self.window_size as i64;
let (mut start, mut end) = (start1, end1);
match self.closed {
WindowClosed::Right => {}
WindowClosed::Left => {
start -= 1;
end -= 1;
}
WindowClosed::Both => {
start -= 1;
}
WindowClosed::Neither => {
end -= 1;
}
}
let start = start.clamp(0, n as i64) as usize;
let end = end.clamp(0, n as i64) as usize;
let window_values: Vec<f64> = if end > start {
values[start..end].iter().filter_map(|&v| v).collect()
} else {
Vec::new()
};
if !window_values.is_empty() && window_values.len() >= min_periods {
trailing.push(Some(func(&window_values)));
} else {
trailing.push(None);
}
}
let result: Vec<Option<R>> = if self.center {
let offset = self.window_size / 2;
(0..n)
.map(|i| {
let src = i + offset;
if src < n {
trailing[src].clone()
} else {
None
}
})
.collect()
} else {
trailing
};
Series::new(result, self.series.name().cloned())
}
}
impl<T> WindowOps<T> for Rolling<T>
where
T: Debug + Clone + Into<f64> + Copy,
{
fn mean(&self) -> Result<Series<f64>> {
let result =
self.apply_window_op(|values| values.iter().sum::<f64>() / values.len() as f64)?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn sum(&self) -> Result<Series<f64>> {
let result = self.apply_window_op(|values| values.iter().sum::<f64>())?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn std(&self, ddof: usize) -> Result<Series<f64>> {
let result = self.apply_window_op(|values| {
if values.len() <= ddof {
f64::NAN
} else {
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance = values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
/ (values.len() - ddof) as f64;
variance.sqrt()
}
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn var(&self, ddof: usize) -> Result<Series<f64>> {
let result = self.apply_window_op(|values| {
if values.len() <= ddof {
f64::NAN
} else {
let mean = values.iter().sum::<f64>() / values.len() as f64;
values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
/ (values.len() - ddof) as f64
}
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn min(&self) -> Result<Series<f64>> {
let result =
self.apply_window_op(|values| values.iter().fold(f64::INFINITY, |a, &b| a.min(b)))?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn max(&self) -> Result<Series<f64>> {
let result =
self.apply_window_op(|values| values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)))?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn count(&self) -> Result<Series<usize>> {
let result = self.apply_window_op(|values| values.len())?;
let values: Vec<usize> = result.values().iter().map(|&v| v.unwrap_or(0)).collect();
Series::new(values, result.name().cloned())
}
fn median(&self) -> Result<Series<f64>> {
let result = self.apply_window_op(|values| {
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mid = sorted.len() / 2;
if sorted.len() % 2 == 0 {
(sorted[mid - 1] + sorted[mid]) / 2.0
} else {
sorted[mid]
}
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn quantile(&self, q: f64) -> Result<Series<f64>> {
if q < 0.0 || q > 1.0 {
return Err(Error::InvalidValue(
"Quantile must be between 0 and 1".to_string(),
));
}
let result = self.apply_window_op(|values| {
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let idx = (q * (sorted.len() - 1) as f64).round() as usize;
sorted[idx.min(sorted.len() - 1)]
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn apply<F, R>(&self, func: F) -> Result<Series<Option<R>>>
where
F: Fn(&[f64]) -> R + Copy,
R: Debug + Clone,
{
self.apply_window_op(func)
}
}
impl<T> Expanding<T>
where
T: Debug + Clone,
{
pub fn new(series: Series<T>, min_periods: usize) -> Result<Self> {
Ok(Self {
series,
min_periods,
})
}
fn values_as_f64(&self) -> Result<Vec<Option<f64>>>
where
T: Into<f64> + Copy,
{
Ok(series_values_as_f64_opt(&self.series))
}
fn apply_expanding_op<F, R>(&self, mut func: F) -> Result<Series<Option<R>>>
where
T: Into<f64> + Copy,
F: FnMut(&[f64]) -> R,
R: Debug + Clone,
{
let values = self.values_as_f64()?;
let mut result = Vec::with_capacity(values.len());
for i in 0..values.len() {
let window_values: Vec<f64> = values[0..=i].iter().filter_map(|&v| v).collect();
if !window_values.is_empty() && window_values.len() >= self.min_periods {
let agg_result = func(&window_values);
result.push(Some(agg_result));
} else {
result.push(None);
}
}
Series::new(result, self.series.name().cloned())
}
}
impl<T> WindowOps<T> for Expanding<T>
where
T: Debug + Clone + Into<f64> + Copy,
{
fn mean(&self) -> Result<Series<f64>> {
let result =
self.apply_expanding_op(|values| values.iter().sum::<f64>() / values.len() as f64)?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn sum(&self) -> Result<Series<f64>> {
let result = self.apply_expanding_op(|values| values.iter().sum::<f64>())?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn std(&self, ddof: usize) -> Result<Series<f64>> {
let result = self.apply_expanding_op(|values| {
if values.len() <= ddof {
f64::NAN
} else {
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance = values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
/ (values.len() - ddof) as f64;
variance.sqrt()
}
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn var(&self, ddof: usize) -> Result<Series<f64>> {
let result = self.apply_expanding_op(|values| {
if values.len() <= ddof {
f64::NAN
} else {
let mean = values.iter().sum::<f64>() / values.len() as f64;
values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
/ (values.len() - ddof) as f64
}
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn min(&self) -> Result<Series<f64>> {
let result =
self.apply_expanding_op(|values| values.iter().fold(f64::INFINITY, |a, &b| a.min(b)))?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn max(&self) -> Result<Series<f64>> {
let result = self
.apply_expanding_op(|values| values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)))?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn count(&self) -> Result<Series<usize>> {
let result = self.apply_expanding_op(|values| values.len())?;
let values: Vec<usize> = result.values().iter().map(|&v| v.unwrap_or(0)).collect();
Series::new(values, result.name().cloned())
}
fn median(&self) -> Result<Series<f64>> {
let result = self.apply_expanding_op(|values| {
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mid = sorted.len() / 2;
if sorted.len() % 2 == 0 {
(sorted[mid - 1] + sorted[mid]) / 2.0
} else {
sorted[mid]
}
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn quantile(&self, q: f64) -> Result<Series<f64>> {
if q < 0.0 || q > 1.0 {
return Err(Error::InvalidValue(
"Quantile must be between 0 and 1".to_string(),
));
}
let result = self.apply_expanding_op(|values| {
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let idx = (q * (sorted.len() - 1) as f64).round() as usize;
sorted[idx.min(sorted.len() - 1)]
})?;
let values: Vec<f64> = result
.values()
.iter()
.map(|&v| v.unwrap_or(f64::NAN))
.collect();
Series::new(values, result.name().cloned())
}
fn apply<F, R>(&self, func: F) -> Result<Series<Option<R>>>
where
F: Fn(&[f64]) -> R + Copy,
R: Debug + Clone,
{
self.apply_expanding_op(func)
}
}
impl<T> EWM<T>
where
T: Debug + Clone,
{
pub fn new(series: Series<T>) -> Self {
Self {
series,
alpha: None,
span: None,
halflife: None,
adjust: true,
ignore_na: false,
min_periods: 0,
}
}
pub fn alpha(mut self, alpha: f64) -> Result<Self> {
if alpha <= 0.0 || alpha > 1.0 {
return Err(Error::InvalidValue(
"Alpha must be between 0 and 1".to_string(),
));
}
self.alpha = Some(alpha);
self.span = None;
self.halflife = None;
Ok(self)
}
pub fn span(mut self, span: usize) -> Self {
self.span = Some(span);
self.alpha = None;
self.halflife = None;
self
}
pub fn halflife(mut self, halflife: f64) -> Self {
self.halflife = Some(halflife);
self.alpha = None;
self.span = None;
self
}
pub fn adjust(mut self, adjust: bool) -> Self {
self.adjust = adjust;
self
}
pub fn ignore_na(mut self, ignore_na: bool) -> Self {
self.ignore_na = ignore_na;
self
}
pub fn min_periods(mut self, min_periods: usize) -> Self {
self.min_periods = min_periods;
self
}
fn get_alpha(&self) -> Result<f64> {
if let Some(alpha) = self.alpha {
Ok(alpha)
} else if let Some(span) = self.span {
Ok(2.0 / (span as f64 + 1.0))
} else if let Some(halflife) = self.halflife {
Ok(1.0 - (-std::f64::consts::LN_2 / halflife).exp())
} else {
Err(Error::InvalidValue(
"Must specify either alpha, span, or halflife".to_string(),
))
}
}
fn values_as_f64(&self) -> Result<Vec<Option<f64>>>
where
T: Into<f64> + Copy,
{
Ok(series_values_as_f64_opt(&self.series))
}
}
struct EwmState {
mean: Vec<f64>,
cov_biased: Vec<f64>,
n_eff: Vec<f64>,
nobs: Vec<usize>,
}
fn ewm_recursion(values: &[Option<f64>], alpha: f64, adjust: bool, ignore_na: bool) -> EwmState {
let n = values.len();
let old_wt_factor = 1.0 - alpha;
let new_wt = if adjust { 1.0 } else { alpha };
let mut mean_out = vec![f64::NAN; n];
let mut cov_out = vec![f64::NAN; n];
let mut n_eff_out = vec![f64::NAN; n];
let mut nobs_out = vec![0usize; n];
let mut mean: Option<f64> = None;
let mut cov = 0.0_f64;
let mut old_wt = 1.0_f64;
let mut sum_wt = 1.0_f64;
let mut sum_wt2 = 1.0_f64;
let mut nobs = 0usize;
for i in 0..n {
let cur = values[i];
let is_obs = cur.is_some();
if is_obs {
nobs += 1;
}
if i == 0 {
if let Some(v) = cur {
mean = Some(v);
cov = 0.0;
}
} else if let Some(m) = mean {
if is_obs || !ignore_na {
old_wt *= old_wt_factor;
sum_wt *= old_wt_factor;
sum_wt2 *= old_wt_factor * old_wt_factor;
}
if let Some(c) = cur {
let old_mean = m;
let new_mean = if old_mean != c {
(old_wt * old_mean + new_wt * c) / (old_wt + new_wt)
} else {
old_mean
};
cov = (old_wt * (cov + (old_mean - new_mean) * (old_mean - new_mean))
+ new_wt * (c - new_mean) * (c - new_mean))
/ (old_wt + new_wt);
mean = Some(new_mean);
sum_wt += new_wt;
sum_wt2 += new_wt * new_wt;
old_wt += new_wt;
if !adjust {
sum_wt /= old_wt;
sum_wt2 /= old_wt * old_wt;
old_wt = 1.0;
}
}
} else if is_obs {
mean = cur;
cov = 0.0;
}
nobs_out[i] = nobs;
if let Some(m) = mean {
mean_out[i] = m;
cov_out[i] = cov;
n_eff_out[i] = if sum_wt2 > 0.0 {
sum_wt * sum_wt / sum_wt2
} else {
f64::NAN
};
}
}
EwmState {
mean: mean_out,
cov_biased: cov_out,
n_eff: n_eff_out,
nobs: nobs_out,
}
}
impl<T> EWM<T>
where
T: Debug + Clone + Into<f64> + Copy,
{
pub fn mean(&self) -> Result<Series<f64>> {
let alpha = self.get_alpha()?;
let values = self.values_as_f64()?;
let state = ewm_recursion(&values, alpha, self.adjust, self.ignore_na);
let result: Vec<f64> = (0..values.len())
.map(|i| {
if state.nobs[i] >= self.min_periods {
state.mean[i]
} else {
f64::NAN
}
})
.collect();
Series::new(result, self.series.name().cloned())
}
pub fn var(&self, ddof: usize) -> Result<Series<f64>> {
let alpha = self.get_alpha()?;
let values = self.values_as_f64()?;
let state = ewm_recursion(&values, alpha, self.adjust, self.ignore_na);
let ddof = ddof as f64;
let result: Vec<f64> = (0..values.len())
.map(|i| {
if state.nobs[i] < self.min_periods {
return f64::NAN;
}
let n_eff = state.n_eff[i];
let denom = n_eff - ddof;
if denom > 0.0 && state.cov_biased[i].is_finite() {
(state.cov_biased[i] * n_eff / denom).max(0.0)
} else {
f64::NAN
}
})
.collect();
Series::new(result, self.series.name().cloned())
}
pub fn std(&self, ddof: usize) -> Result<Series<f64>> {
let var_series = self.var(ddof)?;
let std_values: Vec<f64> = var_series.values().iter().map(|&v| v.sqrt()).collect();
Series::new(std_values, var_series.name().cloned())
}
}
pub trait WindowExt<T>
where
T: Debug + Clone,
{
fn rolling(&self, window_size: usize) -> Result<Rolling<T>>;
fn expanding(&self, min_periods: usize) -> Result<Expanding<T>>;
fn ewm(&self) -> EWM<T>;
}
impl<T> WindowExt<T> for Series<T>
where
T: Debug + Clone,
{
fn rolling(&self, window_size: usize) -> Result<Rolling<T>> {
Rolling::new(self.clone(), window_size)
}
fn expanding(&self, min_periods: usize) -> Result<Expanding<T>> {
Expanding::new(self.clone(), min_periods)
}
fn ewm(&self) -> EWM<T> {
EWM::new(self.clone())
}
}