use anyhow::Result;
use approx::assert_abs_diff_eq;
use ndarray::{Array, Array1, Array2, array};
use ndarray_glm::{Linear, ModelBuilder};
mod common;
use common::{array_from_csv, load_linear_data};
fn pred_test_full_x() -> Array2<f64> {
array![[0.5, 0.3, 0.1], [-1.0, -0.5, 0.8], [2.0, 1.2, -0.3]]
}
fn pred_test_sub_x() -> Array2<f64> {
array![[0.3, 0.1], [-0.5, 0.8], [1.2, -0.3]]
}
fn pred_test_off() -> Array1<f64> {
array![0.15, -0.30, 0.60]
}
fn check_linear_scenario(
fit: &ndarray_glm::Fit<Linear, f64>,
dir: &str,
eps: f64,
has_var_weights: bool,
has_freq_weights: bool,
pred_x: &Array2<f64>,
pred_off: Option<&Array1<f64>>,
) -> Result<()> {
let r = |name: &str| array_from_csv::<f64>(&format!("tests/R/linear_results/{dir}/{name}.csv"));
let n_par = fit.result.len();
let n_obs = 25usize;
let r_coef = r("coefficients")?;
assert_abs_diff_eq!(&fit.result, &r_coef, epsilon = eps);
let r_dev = r("deviance")?[0];
assert_abs_diff_eq!(fit.deviance(), r_dev, epsilon = eps);
let r_null_dev = r("null_deviance")?[0];
let our_null_dev = fit.lr_test() + r_dev;
assert_abs_diff_eq!(our_null_dev, r_null_dev, epsilon = 10. * eps);
let r_pred = r("predict_resp")?;
assert_abs_diff_eq!(fit.predict(pred_x, pred_off), r_pred, epsilon = eps);
let r_rss = r("rss")?[0];
assert_abs_diff_eq!(fit.rss(), r_rss, epsilon = eps);
let r_r_sq = r("r_sq")?[0];
assert_abs_diff_eq!(fit.r_sq(), r_r_sq, epsilon = 10. * eps);
if !has_var_weights {
let r_disp = r("dispersion")?[0];
assert_abs_diff_eq!(fit.dispersion(), r_disp, epsilon = eps);
let r_cov_flat = r("covariance")?;
let r_cov = Array::from_shape_vec((n_par, n_par), r_cov_flat.into_raw_vec_and_offset().0)?;
assert_abs_diff_eq!(fit.covariance()?, &r_cov, epsilon = eps);
let r_wald_z = r("wald_z")?;
assert_abs_diff_eq!(fit.wald_z()?, r_wald_z, epsilon = 10. * eps);
}
let r_resid_resp = r("resid_resp")?;
assert_abs_diff_eq!(fit.resid_resp(), r_resid_resp, epsilon = eps);
let r_resid_work = r("resid_work")?;
assert_abs_diff_eq!(fit.resid_work(), r_resid_work, epsilon = eps);
let partial = fit.resid_part();
let n_feat = partial.ncols();
let has_intercept = n_feat + 1 == n_par;
if !has_var_weights || !has_intercept {
let r_partial_flat = r("resid_partial")?;
let r_partial =
Array::from_shape_vec((n_obs, n_feat), r_partial_flat.into_raw_vec_and_offset().0)?;
assert_abs_diff_eq!(partial, r_partial, epsilon = eps);
}
if !has_freq_weights {
let r_resid_pear = r("resid_pear")?;
assert_abs_diff_eq!(*fit.resid_pear(), r_resid_pear, epsilon = eps);
let r_resid_dev = r("resid_dev")?;
assert_abs_diff_eq!(fit.resid_dev(), r_resid_dev, epsilon = eps);
let r_leverage = r("leverage")?;
assert_abs_diff_eq!(fit.leverage()?, r_leverage, epsilon = eps);
if !has_var_weights {
let r_resid_pear_std = r("resid_pear_std")?;
assert_abs_diff_eq!(fit.resid_pear_std()?, r_resid_pear_std, epsilon = eps);
let r_resid_dev_std = r("resid_dev_std")?;
assert_abs_diff_eq!(fit.resid_dev_std()?, r_resid_dev_std, epsilon = eps);
let _r_resid_student = r("resid_student")?;
let _our_resid_student = fit.resid_student()?;
let r_cooks = r("cooks")?;
assert_abs_diff_eq!(fit.cooks()?, r_cooks, epsilon = eps);
}
let r_loo_flat = r("loo_coef")?;
let r_loo = Array::from_shape_vec((n_obs, n_par), r_loo_flat.into_raw_vec_and_offset().0)?;
let infl = fit.infl_coef()?;
assert_abs_diff_eq!(infl, r_loo, epsilon = 10. * eps);
let loo_exact = fit.loo_exact()?;
let infl_exact = &fit.result - &loo_exact;
assert_abs_diff_eq!(infl, infl_exact, epsilon = eps);
}
#[cfg(feature = "stats")]
{
let r_lr_p = r("pvalue_lr_test")?[0];
assert_abs_diff_eq!(fit.pvalue_lr_test(), r_lr_p, epsilon = eps);
if !has_var_weights {
let r_wald_p = r("pvalue_wald")?;
assert_abs_diff_eq!(fit.pvalue_wald()?, r_wald_p, epsilon = eps);
let r_exact_p = r("pvalue_exact")?;
assert_abs_diff_eq!(fit.pvalue_exact()?, r_exact_p, epsilon = 10. * eps);
}
}
Ok(())
}
#[test]
fn linear_no_weights() -> Result<()> {
let (y, x, _var_wt, _freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit = model.fit()?;
check_linear_scenario(&fit, "none", 1e-10, false, false, &pred_test_full_x(), None)
}
#[test]
fn linear_var_weights() -> Result<()> {
let (y, x, var_wt, _freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x)
.var_weights(var_wt)
.build()?;
let fit = model.fit()?;
check_linear_scenario(&fit, "var", 1e-10, true, false, &pred_test_full_x(), None)
}
#[test]
fn linear_freq_weights() -> Result<()> {
let (y, x, _var_wt, freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x)
.freq_weights(freq_wt)
.build()?;
let fit = model.fit()?;
check_linear_scenario(&fit, "freq", 1e-10, false, true, &pred_test_full_x(), None)
}
#[test]
fn linear_both_weights() -> Result<()> {
let (y, x, var_wt, freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x)
.var_weights(var_wt)
.freq_weights(freq_wt)
.build()?;
let fit = model.fit()?;
check_linear_scenario(&fit, "both", 1e-10, true, true, &pred_test_full_x(), None)
}
fn offset_from_x(x: &ndarray::Array2<f64>) -> ndarray::Array1<f64> {
x.column(0).mapv(|v| 0.3 * v)
}
#[test]
fn linear_offset_noint_no_weights() -> Result<()> {
let (y, x, _var_wt, _freq_wt) = load_linear_data()?;
let off = offset_from_x(&x);
let x_sub = x.slice(ndarray::s![.., 1..]).to_owned();
let model = ModelBuilder::<Linear>::data(&y, &x_sub)
.linear_offset(off)
.no_constant()
.build()?;
let fit = model.fit()?;
let off_pred = pred_test_off();
check_linear_scenario(
&fit,
"off_none",
1e-10,
false,
false,
&pred_test_sub_x(),
Some(&off_pred),
)
}
#[test]
fn linear_offset_noint_var_weights() -> Result<()> {
let (y, x, var_wt, _freq_wt) = load_linear_data()?;
let off = offset_from_x(&x);
let x_sub = x.slice(ndarray::s![.., 1..]).to_owned();
let model = ModelBuilder::<Linear>::data(&y, &x_sub)
.linear_offset(off)
.no_constant()
.var_weights(var_wt)
.build()?;
let fit = model.fit()?;
let off_pred = pred_test_off();
check_linear_scenario(
&fit,
"off_var",
1e-10,
true,
false,
&pred_test_sub_x(),
Some(&off_pred),
)
}
#[test]
fn linear_offset_noint_freq_weights() -> Result<()> {
let (y, x, _var_wt, freq_wt) = load_linear_data()?;
let off = offset_from_x(&x);
let x_sub = x.slice(ndarray::s![.., 1..]).to_owned();
let model = ModelBuilder::<Linear>::data(&y, &x_sub)
.linear_offset(off)
.no_constant()
.freq_weights(freq_wt)
.build()?;
let fit = model.fit()?;
let off_pred = pred_test_off();
check_linear_scenario(
&fit,
"off_freq",
1e-10,
false,
true,
&pred_test_sub_x(),
Some(&off_pred),
)
}
#[test]
fn linear_offset_noint_both_weights() -> Result<()> {
let (y, x, var_wt, freq_wt) = load_linear_data()?;
let off = offset_from_x(&x);
let x_sub = x.slice(ndarray::s![.., 1..]).to_owned();
let model = ModelBuilder::<Linear>::data(&y, &x_sub)
.linear_offset(off)
.no_constant()
.var_weights(var_wt)
.freq_weights(freq_wt)
.build()?;
let fit = model.fit()?;
let off_pred = pred_test_off();
check_linear_scenario(
&fit,
"off_both",
1e-10,
true,
true,
&pred_test_sub_x(),
Some(&off_pred),
)
}
#[test]
fn test_against_self_is_zero() -> Result<()> {
let (y, x, _, _) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit = model.fit()?;
let eps = 64. * f64::EPSILON;
assert_abs_diff_eq!(fit.wald_test_against(&fit.result), 0., epsilon = eps);
assert_abs_diff_eq!(
fit.score_test_against(fit.result.clone())?,
0.,
epsilon = eps
);
Ok(())
}
#[test]
fn counting_weights() -> Result<()> {
use ndarray::array;
let x_duped = array![
[-4.3, 0.2],
[2.3, 0.4],
[2.3, 0.4],
[-1.2, -0.1],
[2.3, 0.4],
[-4.3, 0.2],
[0.5, -0.5],
];
let y_duped = array![0.5, 1.2, 1.2, 0.3, 1.2, 0.5, 0.8];
let x_red = array![[-4.3, 0.2], [2.3, 0.4], [-1.2, -0.1], [0.5, -0.5]];
let y_red = array![0.5, 1.2, 0.3, 0.8];
let freqs_red = array![2, 3, 1, 1];
let model_duped = ModelBuilder::<Linear>::data(&y_duped, &x_duped).build()?;
let fit_duped = model_duped.fit()?;
let model_red = ModelBuilder::<Linear>::data(&y_red, &x_red)
.freq_weights(freqs_red)
.build()?;
let fit_red = model_red.fit()?;
let eps = 64.0 * f64::EPSILON;
assert_abs_diff_eq!(fit_duped.result, fit_red.result, epsilon = eps);
assert_abs_diff_eq!(
fit_duped.covariance()?,
fit_red.covariance()?,
epsilon = eps
);
Ok(())
}