use crate::error::GreenersError;
use crate::linalg::LinalgInverse as _;
use crate::{CovarianceType, OLS};
use ndarray::{s, Array1, Array2};
use std::fmt;
#[derive(Debug)]
pub struct RecursiveLSResult {
pub params_history: Array2<f64>,
pub residuals: Array1<f64>,
pub cusum: Array1<f64>,
pub cusum_squares: Array1<f64>,
pub params: Array1<f64>,
pub n_obs: usize,
}
impl fmt::Display for RecursiveLSResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", " Recursive Least Squares ")?;
writeln!(f, "{:<20} {:>10}", "Observations:", self.n_obs)?;
writeln!(
f,
"{:<20} {:>10}",
"Parameters:",
self.params_history.ncols()
)?;
writeln!(f, "\nFinal coefficients:")?;
for (i, &v) in self.params.iter().enumerate() {
writeln!(f, " beta[{}] = {:.6}", i, v)?;
}
writeln!(f, "{:=^78}", "")
}
}
pub struct RecursiveLS;
impl RecursiveLS {
pub fn fit(y: &Array1<f64>, x: &Array2<f64>) -> Result<RecursiveLSResult, GreenersError> {
let n = y.len();
let k = x.ncols();
if n != x.nrows() {
return Err(GreenersError::ShapeMismatch(
"y and x row count mismatch".into(),
));
}
if n <= k {
return Err(GreenersError::InvalidOperation(
"Need more observations than parameters".into(),
));
}
let x_init = x.slice(s![..k, ..]).to_owned();
let y_init = y.slice(s![..k]).to_owned();
let xtx_inv = x_init.t().dot(&x_init).inv()?;
let mut beta = xtx_inv.dot(&x_init.t().dot(&y_init));
let mut p_mat = xtx_inv;
let mut params_history = Array2::<f64>::zeros((n, k));
let mut residuals = Array1::<f64>::zeros(n);
for t in 0..k {
params_history.row_mut(t).assign(&beta);
}
for t in k..n {
let xt = x.row(t).to_owned();
let yt = y[t];
let y_pred = xt.dot(&beta);
let e = yt - y_pred;
residuals[t] = e;
let px = p_mat.dot(&xt);
let f = 1.0 + xt.dot(&px);
let gain = &px / f;
beta = &beta + &(&gain * e);
let outer = {
let mut m = Array2::<f64>::zeros((k, k));
for i in 0..k {
for j in 0..k {
m[[i, j]] = gain[i] * px[j];
}
}
m
};
p_mat = &p_mat - &outer;
params_history.row_mut(t).assign(&beta);
}
let start = k;
let valid_resid = residuals.slice(s![start..]).to_owned();
let sigma = {
let ss: f64 = valid_resid.iter().map(|r| r * r).sum();
(ss / (n - k) as f64).sqrt()
};
let mut cusum = Array1::<f64>::zeros(n);
let mut cumsum = 0.0;
for t in start..n {
cumsum += residuals[t] / sigma.max(1e-15);
cusum[t] = cumsum;
}
let mut cusum_sq = Array1::<f64>::zeros(n);
let total_ss: f64 = (start..n).map(|t| residuals[t].powi(2)).sum();
let mut partial_ss = 0.0;
for t in start..n {
partial_ss += residuals[t].powi(2);
cusum_sq[t] = partial_ss / total_ss.max(1e-15);
}
Ok(RecursiveLSResult {
params_history,
residuals,
cusum,
cusum_squares: cusum_sq,
params: beta,
n_obs: n,
})
}
}
#[derive(Debug)]
pub struct RollingResult {
pub params_history: Array2<f64>,
pub r_squared_history: Array1<f64>,
pub residuals: Array1<f64>,
pub dates: Vec<String>,
pub variable_names: Option<Vec<String>>,
pub n_obs: usize,
pub window: usize,
}
impl fmt::Display for RollingResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", " Rolling Regression ")?;
writeln!(f, "{:<20} {:>10}", "Observations:", self.n_obs)?;
writeln!(f, "{:<20} {:>10}", "Window:", self.window)?;
writeln!(
f,
"{:<20} {:>10}",
"Parameters:",
self.params_history.ncols()
)?;
if self.n_obs > 0 {
let last = self.params_history.row(self.n_obs - 1);
writeln!(f, "\nLast window coefficients:")?;
for (i, &v) in last.iter().enumerate() {
writeln!(f, " beta[{}] = {:.6}", i, v)?;
}
}
writeln!(f, "{:=^78}", "")
}
}
pub struct RollingOLS;
impl RollingOLS {
pub fn fit(
y: &Array1<f64>,
x: &Array2<f64>,
window: usize,
dates: Option<&[String]>,
variable_names: Option<Vec<String>>,
) -> Result<RollingResult, GreenersError> {
let n = y.len();
let k = x.ncols();
if n != x.nrows() {
return Err(GreenersError::ShapeMismatch(
"y and x row count mismatch".into(),
));
}
if let Some(d) = dates {
if d.len() != n {
return Err(GreenersError::ShapeMismatch(
"dates length must match y length".into(),
));
}
}
if window <= k {
return Err(GreenersError::InvalidOperation(
"Window must be larger than number of parameters".into(),
));
}
if window > n {
return Err(GreenersError::InvalidOperation(
"Window larger than sample size".into(),
));
}
let mut params_history = Array2::<f64>::from_elem((n, k), f64::NAN);
let mut r_squared_history = Array1::<f64>::from_elem(n, f64::NAN);
let mut residuals = Array1::<f64>::from_elem(n, f64::NAN);
for t in (window - 1)..n {
let start = t + 1 - window;
let y_win = y.slice(s![start..=t]).to_owned();
let x_win = x.slice(s![start..=t, ..]).to_owned();
if let Ok(ols) = OLS::fit(&y_win, &x_win, CovarianceType::NonRobust) {
params_history.row_mut(t).assign(&ols.params);
r_squared_history[t] = ols.r_squared;
let fitted = x_win.dot(&ols.params);
residuals[t] = y_win[window - 1] - fitted[window - 1];
}
}
let dates = dates.map(|d| d.to_vec()).unwrap_or_default();
Ok(RollingResult {
params_history,
r_squared_history,
residuals,
dates,
variable_names,
n_obs: n,
window,
})
}
}
pub struct RollingWLS;
impl RollingWLS {
pub fn fit(
y: &Array1<f64>,
x: &Array2<f64>,
window: usize,
weights: &Array1<f64>,
dates: Option<&[String]>,
variable_names: Option<Vec<String>>,
) -> Result<RollingResult, GreenersError> {
let n = y.len();
let k = x.ncols();
if n != x.nrows() || n != weights.len() {
return Err(GreenersError::ShapeMismatch(
"y, x, and weights length mismatch".into(),
));
}
if let Some(d) = dates {
if d.len() != n {
return Err(GreenersError::ShapeMismatch(
"dates length must match y length".into(),
));
}
}
if window <= k {
return Err(GreenersError::InvalidOperation(
"Window must be larger than number of parameters".into(),
));
}
if window > n {
return Err(GreenersError::InvalidOperation(
"Window larger than sample size".into(),
));
}
let mut params_history = Array2::<f64>::from_elem((n, k), f64::NAN);
let mut r_squared_history = Array1::<f64>::from_elem(n, f64::NAN);
let mut residuals = Array1::<f64>::from_elem(n, f64::NAN);
for t in (window - 1)..n {
let start = t + 1 - window;
let y_win = y.slice(s![start..=t]).to_owned();
let x_win = x.slice(s![start..=t, ..]).to_owned();
let w_win = weights.slice(s![start..=t]).to_owned();
let sqrt_w = w_win.mapv(f64::sqrt);
let y_t: Array1<f64> = &y_win * &sqrt_w;
let mut x_t = x_win.clone();
for (i, mut row) in x_t.axis_iter_mut(ndarray::Axis(0)).enumerate() {
row *= sqrt_w[i];
}
if let Ok(ols) = OLS::fit(&y_t, &x_t, CovarianceType::NonRobust) {
params_history.row_mut(t).assign(&ols.params);
r_squared_history[t] = ols.r_squared;
let fitted = x_win.dot(&ols.params);
residuals[t] = y_win[window - 1] - fitted[window - 1];
}
}
let dates = dates.map(|d| d.to_vec()).unwrap_or_default();
Ok(RollingResult {
params_history,
r_squared_history,
residuals,
dates,
variable_names,
n_obs: n,
window,
})
}
}