r2rs-stats 0.1.1

Statistics programming for Rust based on R's stats package
Documentation
// "Whatever you do, work at it with all your heart, as working for the Lord,
// not for human masters, since you know that you will receive an inheritance
// from the Lord as a reward. It is the Lord Christ you are serving."
// (Col 3:23-24)

use std::str::FromStr;

use nalgebra::DMatrix;
use num_traits::ToPrimitive;
use strafe_testing::{
    r::{RGenerator, RString, RTester},
    r_assert_relative_equal,
};

use crate::regression::glm::{family::core::Family, link::core::Link};

pub fn family_option_string(family_option: &str) -> String {
    match family_option {
        "Binomial" => "binomial()".to_string(),
        "Gamma" => "Gamma()".to_string(),
        "Gaussian" => "gaussian()".to_string(),
        "InverseGaussian" => "inverse.gaussian()".to_string(),
        "Poisson" => "poisson()".to_string(),
        "Quasi(Constant)" => "quasi(variance = 'constant')".to_string(),
        "Quasi(Mu)" => "quasi(variance = 'mu')".to_string(),
        "Quasi(MuSqrd)" => "quasi(variance = 'mu^2')".to_string(),
        "Quasi(MuCubed)" => "quasi(variance = 'mu^3')".to_string(),
        "Quasi(MuOneMinusMu)" => "quasi(variance = 'mu(1-mu)')".to_string(),
        "QuasiBinomial" => "quasibinomial()".to_string(),
        "QuasiPoisson" => "quasipoisson()".to_string(),
        _ => unimplemented!(),
    }
}

pub fn family_test_inner<L: Link, F: Family<L>>(
    seed: u16,
    num_rows: f64,
    family: F,
    family_string: &str,
) {
    let num_rows = num_rows.to_usize().unwrap();

    let ys = RTester::new()
        .set_seed(seed)
        .set_display(&RString::from_string(format!("runif({}, 0, 1)", num_rows,)))
        .run()
        .expect("R error")
        .as_f64_vec()
        .expect("R array error");

    let mut weights = RGenerator::new()
        .set_seed(seed + 1)
        .generate_uniform_matrix(num_rows, 1, (0, 10));
    let mut y = DMatrix::from_iterator(ys.len(), 1, ys.iter().copied());

    let library = RString::from_library_name("stats");

    let r_n = RTester::new()
        .add_library(&library)
        .set_script(&RString::from_string(format!(
            "y={0};etastart={0};weights={1};nobs={2};eval({3}$initialize);",
            RString::from_f64_slice(&ys),
            RString::from_f64_matrix(&weights),
            RString::from_f64(ys.len()),
            RString::from_str(&family_string).unwrap(),
        )))
        .set_display(&RString::from_str("n").unwrap())
        .run()
        .expect("R error")
        .as_f64_vec()
        .expect("Could not read vector");
    let r_mu_start = RTester::new()
        .add_library(&library)
        .set_script(&RString::from_string(format!(
            "y={0};etastart={0};weights={1};nobs={2};eval({3}$initialize);",
            RString::from_f64_slice(&ys),
            RString::from_f64_matrix(&weights),
            RString::from_f64(ys.len()),
            RString::from_str(&family_string).unwrap(),
        )))
        .set_display(&RString::from_str("mustart").unwrap())
        .run()
        .expect("R error")
        .as_f64_matrix()
        .expect("Could not read matrix");
    let (rust_n, rust_mu_start) = family.initialize(&mut y, &mut weights);

    for (&r_val, &rust_val) in r_n.iter().zip(rust_n.iter()) {
        r_assert_relative_equal!(r_val, rust_val, 1e-4);
    }
    for (&r_val, &rust_val) in r_mu_start.iter().zip(rust_mu_start.iter()) {
        r_assert_relative_equal!(r_val, rust_val, 1e-4);
    }

    // Variance
    let r_variance = RTester::new()
        .add_library(&library)
        .set_script(&RString::from_string(format!(
            "mu={};fam={};",
            RString::from_f64_matrix(&rust_mu_start),
            RString::from_str(&family_string).unwrap(),
        )))
        .set_display(&RString::from_str("fam$variance(mu)").unwrap())
        .run()
        .expect("R error")
        .as_f64_matrix()
        .expect("Could not read matrix");

    let rust_variance = family.variance(&rust_mu_start);

    for (&r_val, &rust_val) in r_variance.iter().zip(rust_variance.iter()) {
        r_assert_relative_equal!(r_val, rust_val, 1e-4);
    }

    // Valid Mu
    let r_valid_mu = RTester::new()
        .add_library(&library)
        .set_script(&RString::from_string(format!(
            "mu={};fam={};",
            RString::from_f64_matrix(&rust_mu_start),
            RString::from_str(&family_string).unwrap(),
        )))
        .set_display(&RString::from_str("fam$validmu(mu)").unwrap())
        .run()
        .expect("R error")
        .as_bool()
        .expect("Could not read bool");

    let rust_valid_mu = family.valid_mu(&rust_mu_start);

    assert_eq!(r_valid_mu, rust_valid_mu);

    // Residual Deviance
    let r_residual_deviance = RTester::new()
        .add_library(&library)
        .set_script(&RString::from_string(format!(
            "y={};mu={};w={};fam={};",
            RString::from_f64_slice(&ys),
            RString::from_f64_matrix(&rust_mu_start),
            RString::from_f64_matrix(&weights),
            RString::from_str(&family_string).unwrap(),
        )))
        .set_display(&RString::from_str("fam$dev.resids(y,mu,w)").unwrap())
        .run()
        .expect("R error")
        .as_f64_vec()
        .expect("Could not read vec");

    let rust_residual_deviance = family.residual_deviance(&y, &rust_mu_start, &weights);

    for (&r_val, &rust_val) in r_residual_deviance
        .iter()
        .zip(rust_residual_deviance.iter())
    {
        r_assert_relative_equal!(r_val, rust_val, 1e-4);
    }

    let r_aic = RTester::new()
        .add_library(&library)
        .set_script(&RString::from_string(format!(
            "y={};n={};mu={};w={};dev={};fam={};",
            RString::from_f64_slice(&ys),
            RString::from_f64_matrix(&rust_n),
            RString::from_f64_matrix(&rust_mu_start),
            RString::from_f64_matrix(&weights),
            RString::from_f64(rust_residual_deviance.sum()),
            RString::from_str(&family_string).unwrap(),
        )))
        .set_display(&RString::from_str("fam$aic(y,n,mu,w,dev)").unwrap())
        .run()
        .expect("R error")
        .as_f64()
        .expect("Could not read float");
    let rust_aic = family.aic(
        y.as_slice(),
        rust_n.as_slice(),
        rust_mu_start.as_slice(),
        weights.as_slice(),
        rust_residual_deviance.sum(),
    );

    r_assert_relative_equal!(r_aic, rust_aic, 1e-4);
}