use ndarray::{Array1, Array2, s};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[cfg(feature = "plotly")]
use plotly::{Plot, Layout, Scatter, common::{Line, color::NamedColor}, layout::{Axis, Shape, ShapeType, ShapeLine}};
use crate::error::DigiFiError;
use crate::statistics::pearson_correlation;
pub fn autocorrelation(array: &Array1<f64>, n_lag: usize) -> Result<f64, DigiFiError> {
let array_len: usize = array.len();
if array_len <= n_lag {
return Err(DigiFiError::ValidationError {
title: "Autocorrelation".to_owned(),
details: "Number of lags used for autocorrelation exceeds the number of data points in the array.".to_owned(),
});
}
let array_t: Array1<f64> = array.slice(s![0..(array_len - n_lag)]).to_owned();
let array_t_plus_n: Array1<f64> = array.slice(s![n_lag..array_len]).to_owned();
pearson_correlation(&array_t, &array_t_plus_n, 0)
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Autocorrelation {
pub autocorrelations: Array1<f64>,
pub lags: Vec<usize>,
}
pub fn autocorrelation_array(array: &Array1<f64>, max_lag: usize) -> Result<Autocorrelation, DigiFiError> {
let n_acs: usize = max_lag + 1;
let mut acs: Vec<f64> = Vec::with_capacity(n_acs);
let mut lags: Vec<usize> = Vec::with_capacity(n_acs);
for n in (0..=max_lag).into_iter() {
match n {
0 => {
acs.push(1.0);
lags.push(0);
},
_ => {
acs.push(autocorrelation(array, n)?);
lags.push(n);
}
}
}
Ok(Autocorrelation {
autocorrelations: Array1::from_vec(acs),
lags,
})
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PartialAutocorrelation {
pub partial_autocorrelations: Array1<f64>,
pub lags: Vec<usize>,
pub levinson_durbin_phis: Array2<f64>,
}
pub fn partial_autocorrelation(array: &Array1<f64>, max_lag: usize) -> Result<PartialAutocorrelation, DigiFiError> {
let n_pacs: usize = max_lag + 1;
let mut pacs: Vec<f64> = Vec::with_capacity(n_pacs);
let mut lags: Vec<usize> = Vec::with_capacity(n_pacs);
let mut phis: Array2<f64> = Array2::from_shape_fn((n_pacs, n_pacs), |_| { f64::NAN } );
for n in (0..=max_lag).into_iter() {
match n {
0 => {
phis[[0,0]] = 1.0;
pacs.push(1.0);
},
1 => {
let phi: f64 = autocorrelation(array, 1)?;
phis[[1,1]] = phi;
pacs.push(phi);
},
_ => {
let n_minus_one: usize = n - 1;
let n_minus_two: usize = n - 2;
let mut numerator_sum: f64 = 0.0;
let mut denominator_sum: f64 = 0.0;
for k in (1..=n_minus_one).into_iter() {
if n_minus_one!=k {
phis[[n_minus_one, k]] = phis[[n_minus_two, k]] - phis[[n_minus_one, n_minus_one]] * phis[[n_minus_two, n_minus_one - k]];
}
numerator_sum += phis[[n_minus_one, k]] * autocorrelation(array, n - k)?;
denominator_sum += phis[[n_minus_one, k]] * autocorrelation(array, k)?;
}
let phi: f64 = (autocorrelation(array, n)? - numerator_sum) / (1.0 - denominator_sum);
phis[[n, n]] = phi;
pacs.push(phi);
},
}
lags.push(n);
}
Ok(PartialAutocorrelation {
partial_autocorrelations: Array1::from_vec(pacs),
lags,
levinson_durbin_phis: phis,
})
}
#[cfg(feature = "plotly")]
pub fn plot_autocorrelation(autocorrelations: Array1<f64>, lags: Vec<usize>, partial: bool, upper_crit: Option<f64>, lower_crit: Option<f64>) -> Plot {
let title: String = if partial { String::from("Partial Autocorrelation") } else { String::from("Autocorrelation") };
let mut plot: Plot = Plot::new();
if let Some(upper_crit) = upper_crit {
let upper_crit_line: Line = Line::new().color(NamedColor::Blue);
plot.add_trace(Scatter::new(lags.clone(), vec![upper_crit; lags.len()]).name("Upper Critical Value").line(upper_crit_line));
}
if let Some(lower_crit) = lower_crit {
let lower_crit_line: Line = Line::new().color(NamedColor::Blue);
plot.add_trace(Scatter::new(lags.clone(), vec![lower_crit; lags.len()]).name("Lower Critical Value").line(lower_crit_line));
}
let x_axis: Axis = Axis::new().title("Lag").range(vec![-1, lags[lags.len() - 1] as i64 + 1]).zero_line(false);
let y_axis: Axis = Axis::new().title(&title).range(vec![-1.0, 1.0]);
let mut layout: Layout = Layout::new().title(format!("<b>{}</b>", title)).x_axis(x_axis).y_axis(y_axis);
for (ac, lag) in autocorrelations.iter().zip(lags.iter()) {
let shape: Shape = Shape::new()
.shape_type(ShapeType::Line)
.x0(*lag).x1(*lag)
.y0(0.0).y1(*ac)
.line(ShapeLine::new().color(NamedColor::Black).width(2.0));
layout.add_shape(shape);
}
plot.set_layout(layout);
plot
}
#[cfg(test)]
mod tests {
use ndarray::Array1;
use crate::utilities::TEST_ACCURACY;
#[test]
fn unit_test_autocorrelation() -> () {
use crate::statistics::autocorrelation::autocorrelation;
let array: Array1<f64> = Array1::from_vec(vec![0.0, 0.25, 0.15, -0.45, 0.3, -0.05, 0.12, 0.07]);
assert!((autocorrelation(&array, 1).unwrap() - -0.5160922669123634).abs() < TEST_ACCURACY);
assert!((autocorrelation(&array, 2).unwrap() - -0.04864833358838002).abs() < TEST_ACCURACY);
assert!((autocorrelation(&array, 3).unwrap() - 0.1033387585496719).abs() < TEST_ACCURACY);
assert!((autocorrelation(&array, 4).unwrap() - -0.15200675246896855).abs() < TEST_ACCURACY);
}
#[test]
fn unit_test_autocorrelation_array() -> () {
use crate::statistics::autocorrelation::{Autocorrelation, autocorrelation_array};
let array: Array1<f64> = Array1::from_vec(vec![0.0, 0.25, 0.15, -0.45, 0.3, -0.05, 0.12, 0.07]);
let acs: Autocorrelation = autocorrelation_array(&array, 4).unwrap();
let tested_result: Array1<f64> = Array1::from_vec(vec![1.0, -0.5160922669123634, -0.04864833358838002, 0.1033387585496719, -0.15200675246896855]);
assert!((acs.autocorrelations - tested_result).map(|v| v.abs() ).sum() < TEST_ACCURACY);
}
#[test]
fn unit_test_partial_autocorrelation() -> () {
use crate::statistics::autocorrelation::{PartialAutocorrelation, partial_autocorrelation};
let array: Array1<f64> = Array1::from_vec(vec![0.0, 0.25, 0.15, -0.45, 0.3, -0.05, 0.12, 0.07]);
let pacs: PartialAutocorrelation = partial_autocorrelation(&array, 4).unwrap();
let tested_result: Array1<f64> = Array1::from_vec(vec![1.0, -0.51428607, -0.41256983, -0.24459419, -0.290511]);
assert!((pacs.partial_autocorrelations - tested_result).map(|v| v.abs() ).sum() < 20_000_000.0 * TEST_ACCURACY);
}
#[cfg(feature = "plotly")]
#[test]
#[ignore]
fn unit_test_plot_autocorrelation() -> () {
use plotly::Plot;
use crate::statistics::autocorrelation::{Autocorrelation, autocorrelation_array, plot_autocorrelation};
let array: Array1<f64> = Array1::from_vec(vec![0.0, 0.25, 0.15, -0.45, 0.3, -0.05, 0.12, 0.07]);
let acs: Autocorrelation = autocorrelation_array(&array, 4).unwrap();
let plot: Plot = plot_autocorrelation(acs.autocorrelations, acs.lags, false, Some(0.15), Some(-0.15));
plot.show();
}
#[cfg(feature = "plotly")]
#[test]
#[ignore]
fn unit_test_plot_partial_autocorrelation() -> () {
use plotly::Plot;
use crate::statistics::autocorrelation::{PartialAutocorrelation, partial_autocorrelation, plot_autocorrelation};
let array: Array1<f64> = Array1::from_vec(vec![0.0, 0.25, 0.15, -0.45, 0.3, -0.05, 0.12, 0.07]);
let pacs: PartialAutocorrelation = partial_autocorrelation(&array, 4).unwrap();
let plot: Plot = plot_autocorrelation(pacs.partial_autocorrelations, pacs.lags, true, Some(0.15), Some(-0.15));
plot.show();
}
}