use ndarray::{Array1, Array2, Axis, s};
use crate::{OLS, CovarianceType, GreenersError};
use std::fmt;
#[derive(Debug)]
pub struct FglsResult {
pub method: String, pub params: Array1<f64>,
pub std_errors: Array1<f64>,
pub t_values: Array1<f64>,
pub p_values: Array1<f64>,
pub r_squared: f64,
pub rho: Option<f64>, pub iter: Option<usize>,}
impl fmt::Display for FglsResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", format!(" FGLS Estimation ({}) ", self.method))?;
if let Some(r) = self.rho {
writeln!(f, "Autocorrelation (rho): {:>10.4}", r)?;
}
writeln!(f, "{:<20} {:>15.4}", "R-squared:", self.r_squared)?;
writeln!(f, "\n{:-^78}", "")?;
writeln!(f, "{:<10} | {:>10} | {:>10} | {:>8} | {:>8}",
"Variable", "coef", "std err", "t", "P>|t|")?;
writeln!(f, "{:-^78}", "")?;
for i in 0..self.params.len() {
writeln!(f, "x{:<9} | {:>10.4} | {:>10.4} | {:>8.3} | {:>8.3}",
i, self.params[i], self.std_errors[i], self.t_values[i], self.p_values[i]
)?;
}
writeln!(f, "{:=^78}", "")
}
}
pub struct FGLS;
impl FGLS {
pub fn wls(
y: &Array1<f64>,
x: &Array2<f64>,
weights: &Array1<f64>
) -> Result<FglsResult, GreenersError> {
let n = y.len();
if weights.len() != n {
return Err(GreenersError::ShapeMismatch("Weights length mismatch".into()));
}
let sqrt_w = weights.mapv(f64::sqrt);
let y_transformed = y * &sqrt_w;
let mut x_transformed = x.clone();
for (i, mut row) in x_transformed.axis_iter_mut(Axis(0)).enumerate() {
row *= sqrt_w[i];
}
let ols = OLS::fit(&y_transformed, &x_transformed, CovarianceType::NonRobust)?;
Ok(FglsResult {
method: "WLS".to_string(),
params: ols.params,
std_errors: ols.std_errors,
t_values: ols.t_values,
p_values: ols.p_values,
r_squared: ols.r_squared,
rho: None,
iter: None,
})
}
pub fn cochrane_orcutt(
y: &Array1<f64>,
x: &Array2<f64>
) -> Result<FglsResult, GreenersError> {
let n = y.len();
let tol = 1e-6;
let max_iter = 100;
let initial_ols = OLS::fit(y, x, CovarianceType::NonRobust)?;
let mut residuals = y - &x.dot(&initial_ols.params);
let mut rho = 0.0;
let mut iter = 0;
let mut diff = 1.0;
let mut final_ols = initial_ols;
while diff > tol && iter < max_iter {
let old_rho = rho;
let u_t = residuals.slice(s![1..]).to_owned();
let u_tm1 = residuals.slice(s![..n-1]).to_owned();
let num = u_tm1.dot(&u_t);
let den = u_tm1.dot(&u_tm1);
rho = num / den;
let y_t = y.slice(s![1..]);
let y_tm1 = y.slice(s![..n-1]);
let y_star = &y_t - &(&y_tm1 * rho);
let x_t = x.slice(s![1.., ..]);
let x_tm1 = x.slice(s![..n-1, ..]);
let x_star = &x_t - &(&x_tm1 * rho);
final_ols = OLS::fit(&y_star.to_owned(), &x_star.to_owned(), CovarianceType::NonRobust)?;
residuals = y - &x.dot(&final_ols.params);
diff = (rho - old_rho).abs();
iter += 1;
}
Ok(FglsResult {
method: "Cochrane-Orcutt AR(1)".to_string(),
params: final_ols.params,
std_errors: final_ols.std_errors,
t_values: final_ols.t_values,
p_values: final_ols.p_values,
r_squared: final_ols.r_squared,
rho: Some(rho),
iter: Some(iter),
})
}
}