use ndarray::{Array1, Array2};
use serde_json::Value;
use solow_regression::LinearModel;
use solow_stats::{
acorr_breusch_godfrey, acorr_lm, compare_f_test, compare_lr_test, het_arch, linear_reset,
ResetAug,
};
use std::fs;
fn load() -> Value {
let p = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../tests/fixtures/stats_ext3.json"
);
let s = fs::read_to_string(p).expect("fixture present (run tools/reference/gen_stats_ext3.py)");
serde_json::from_str(&s).unwrap()
}
fn vec1(v: &Value) -> Array1<f64> {
Array1::from_vec(
v.as_array()
.unwrap()
.iter()
.map(|x| x.as_f64().unwrap())
.collect(),
)
}
fn mat(v: &Value) -> Array2<f64> {
let rows: Vec<Vec<f64>> = v
.as_array()
.unwrap()
.iter()
.map(|r| {
r.as_array()
.unwrap()
.iter()
.map(|x| x.as_f64().unwrap())
.collect()
})
.collect();
let (m, n) = (rows.len(), rows[0].len());
Array2::from_shape_vec((m, n), rows.into_iter().flatten().collect()).unwrap()
}
fn rel(got: f64, want: f64) -> f64 {
(got - want).abs() / (1.0 + want.abs())
}
fn assert_rel(label: &str, got: f64, want: f64, tol: f64) {
let e = rel(got, want);
assert!(
e <= tol,
"{label}: rel-err {e:.3e} (got {got}, want {want})"
);
}
fn nlags_of(v: &Value) -> Option<usize> {
v.as_u64().map(|x| x as usize)
}
#[test]
fn ols_diagnostics_match_reference() {
let fx = load();
for case in fx["diagnostics"].as_array().unwrap() {
let name = case["name"].as_str().unwrap();
let endog = vec1(&case["endog"]);
let exog = mat(&case["exog"]);
let res = LinearModel::ols(endog.clone(), exog.clone())
.unwrap()
.fit()
.unwrap();
let resid = res.resid.clone();
let ref_resid = vec1(&case["resid"]);
for i in 0..resid.len() {
assert_rel(&format!("{name}.resid[{i}]"), resid[i], ref_resid[i], 1e-8);
}
for (bi, b) in case["bg"].as_array().unwrap().iter().enumerate() {
let nl = nlags_of(&b["nlags"]);
let (lm, lmp, fv, fp) = acorr_breusch_godfrey(&resid, &exog, nl).unwrap();
let lab = format!("{name}.bg[{bi}]");
assert_rel(&format!("{lab}.lm"), lm, b["lm"].as_f64().unwrap(), 1e-8);
assert_rel(
&format!("{lab}.lm_pvalue"),
lmp,
b["lm_pvalue"].as_f64().unwrap(),
1e-6,
);
assert_rel(
&format!("{lab}.fvalue"),
fv,
b["fvalue"].as_f64().unwrap(),
1e-8,
);
assert_rel(
&format!("{lab}.f_pvalue"),
fp,
b["f_pvalue"].as_f64().unwrap(),
1e-6,
);
}
for (li, b) in case["acorr_lm"].as_array().unwrap().iter().enumerate() {
let nl = nlags_of(&b["nlags"]);
let (lm, lmp, fv, fp) = acorr_lm(&resid, nl, 0).unwrap();
let lab = format!("{name}.acorr_lm[{li}]");
assert_rel(&format!("{lab}.lm"), lm, b["lm"].as_f64().unwrap(), 1e-8);
assert_rel(
&format!("{lab}.lm_pvalue"),
lmp,
b["lm_pvalue"].as_f64().unwrap(),
1e-6,
);
assert_rel(
&format!("{lab}.fvalue"),
fv,
b["fvalue"].as_f64().unwrap(),
1e-8,
);
assert_rel(
&format!("{lab}.f_pvalue"),
fp,
b["f_pvalue"].as_f64().unwrap(),
1e-6,
);
}
for (ai, b) in case["het_arch"].as_array().unwrap().iter().enumerate() {
let nl = nlags_of(&b["nlags"]);
let (lm, lmp, fv, fp) = het_arch(&resid, nl, 0).unwrap();
let lab = format!("{name}.het_arch[{ai}]");
assert_rel(&format!("{lab}.lm"), lm, b["lm"].as_f64().unwrap(), 1e-8);
assert_rel(
&format!("{lab}.lm_pvalue"),
lmp,
b["lm_pvalue"].as_f64().unwrap(),
1e-6,
);
assert_rel(
&format!("{lab}.fvalue"),
fv,
b["fvalue"].as_f64().unwrap(),
1e-8,
);
assert_rel(
&format!("{lab}.f_pvalue"),
fp,
b["f_pvalue"].as_f64().unwrap(),
1e-6,
);
}
for (ri, b) in case["reset"].as_array().unwrap().iter().enumerate() {
let power = b["power"].as_u64().unwrap() as usize;
let ttype = match b["test_type"].as_str().unwrap() {
"fitted" => ResetAug::Fitted,
"exog" => ResetAug::Exog,
other => panic!("unknown reset test_type {other}"),
};
let use_f = b["use_f"].as_bool().unwrap();
let (stat, pv) = linear_reset(&endog, &exog, power, ttype, use_f).unwrap();
let lab = format!("{name}.reset[{ri}]");
assert_rel(
&format!("{lab}.statistic"),
stat,
b["statistic"].as_f64().unwrap(),
1e-8,
);
assert_rel(
&format!("{lab}.pvalue"),
pv,
b["pvalue"].as_f64().unwrap(),
1e-6,
);
}
}
}
#[test]
fn nested_comparisons_match_reference() {
let fx = load();
for case in fx["compare"].as_array().unwrap() {
let name = case["name"].as_str().unwrap();
let endog = vec1(&case["endog"]);
let exog_full = mat(&case["exog_full"]);
let exog_restr = mat(&case["exog_restricted"]);
let full = LinearModel::ols(endog.clone(), exog_full)
.unwrap()
.fit()
.unwrap();
let restr = LinearModel::ols(endog, exog_restr).unwrap().fit().unwrap();
let (lr, lrp, lrdf) = compare_lr_test(&full, &restr);
assert_rel(
&format!("{name}.lr_stat"),
lr,
case["lr_stat"].as_f64().unwrap(),
1e-8,
);
assert_rel(
&format!("{name}.lr_pvalue"),
lrp,
case["lr_pvalue"].as_f64().unwrap(),
1e-6,
);
assert_rel(
&format!("{name}.lr_df"),
lrdf,
case["lr_df"].as_f64().unwrap(),
1e-12,
);
let (f, fp, fdf) = compare_f_test(&full, &restr);
assert_rel(
&format!("{name}.f_value"),
f,
case["f_value"].as_f64().unwrap(),
1e-8,
);
assert_rel(
&format!("{name}.f_pvalue"),
fp,
case["f_pvalue"].as_f64().unwrap(),
1e-6,
);
assert_rel(
&format!("{name}.f_df"),
fdf,
case["f_df"].as_f64().unwrap(),
1e-12,
);
}
}