mod common;
use anyhow::Result;
use approx::assert_abs_diff_eq;
use common::{array_from_csv, load_linear_data, y_x_from_iris};
use ndarray::{Array1, Array2, Axis, array};
use ndarray_glm::{Linear, Logistic, ModelBuilder};
#[test]
fn same_lin_intercept() -> Result<()> {
let y_data: Array1<f64> = array![0.3, 0.5, 0.8, 0.2];
let x_data: Array2<f64> = array![[1.5, 0.6], [2.1, 0.8], [1.2, 0.7], [1.6, 0.3]];
let x_data = x_data.clone() - x_data.mean_axis(Axis(0)).unwrap();
let lin_model = ModelBuilder::<Linear>::data(&y_data, &x_data).build()?;
let lin_fit = lin_model.fit()?;
let lin_model_reg = ModelBuilder::<Linear>::data(&y_data, &x_data).build()?;
let lin_fit_reg = lin_model_reg.fit_options().l2_reg(1.0).fit()?;
dbg!(&lin_fit.result);
dbg!(&lin_fit_reg.result);
assert_abs_diff_eq!(
lin_fit.result[0],
lin_fit_reg.result[0],
epsilon = 2.0 * f64::EPSILON
);
Ok(())
}
#[test]
fn lasso_underconstrained() -> Result<()> {
let y_data: Array1<bool> = array![true, false, true];
let x_data: Array2<f64> = array![[0.1, 1.5, 8.0], [-0.1, 1.0, -12.0], [0.2, 0.5, 9.5]];
let model = ModelBuilder::<Logistic>::data(&y_data, &x_data).build()?;
let fit = model.fit_options().max_iter(256).l1_reg(1.0).fit()?;
let like: f64 = fit.model_like;
assert!(like.is_normal());
Ok(())
}
#[test]
fn elnet_seperable() -> Result<()> {
let (y_labels, x_data) = y_x_from_iris()?;
let y_data: Array1<bool> = y_labels.mapv(|i| i == 0);
let target: Array1<f32> =
array_from_csv("tests/R/log_regularization/iris_setosa_l1_l2_1e-2.csv")?;
let model = ModelBuilder::<Logistic>::data(&y_data, &x_data).build()?;
let fit = model.fit_options().l1_reg(1e-2).l2_reg(1e-2).fit()?;
assert!(
fit.lr_test_against(&target) >= 0.,
"If it's not an exact match to the target, it should be a better result under our likelihood."
);
assert_abs_diff_eq!(&target, &fit.result, epsilon = 0.01);
let target_nostd: Array1<f32> =
array_from_csv("tests/R/log_regularization/iris_setosa_l1_l2_1e-2_nostd.csv")?;
let model_std = ModelBuilder::<Logistic>::data(&y_data, &x_data)
.no_standardize()
.build()?;
let fit_nostd = model_std.fit_options().l1_reg(1e-2).l2_reg(1e-2).fit()?;
assert_abs_diff_eq!(&target_nostd, &fit_nostd.result, epsilon = 0.05);
Ok(())
}
#[test]
fn ridge_seperable() -> Result<()> {
let (y_labels, x_data) = y_x_from_iris()?;
let y_data: Array1<bool> = y_labels.mapv(|i| i == 0);
let target: Array1<f32> =
array_from_csv("tests/R/log_regularization/iris_setosa_l2_1e-2_nostd.csv")?;
let model = ModelBuilder::<Logistic>::data(&y_data, &x_data)
.no_standardize()
.build()?;
let fit = model.fit_options().l2_reg(1e-2).fit()?;
assert!(fit.lr_test_against(&target) > -f32::EPSILON);
assert_abs_diff_eq!(&target, &fit.result, epsilon = 2e-3);
Ok(())
}
#[test]
fn lasso_versicolor() -> Result<()> {
let (y_labels, x_data) = y_x_from_iris()?;
let y_data: Array1<bool> = y_labels.mapv(|i| i == 1);
let target: Array1<f32> =
array_from_csv("tests/R/log_regularization/iris_versicolor_l1_1e-2.csv")?;
let model = ModelBuilder::<Logistic>::data(&y_data, &x_data).build()?;
let fit = model.fit_options().l1_reg(1e-2).fit()?;
assert!(
fit.lr_test_against(&target) >= 0.,
"If it's not an exact match to the target, it should be a better result under our likelihood."
);
assert_abs_diff_eq!(&target, &fit.result, epsilon = 0.01);
Ok(())
}
#[test]
fn linear_ridge() -> Result<()> {
let (y, x, _var_wt, _freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit_unreg = model.fit()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit = model.fit_options().l2_reg(0.1).fit()?;
assert!(fit.n_iter <= 32, "Ridge should converge");
assert!(
fit.lr_test_against(&fit_unreg.result) >= 0.,
"Ridge fit should be at least as good under its own penalized likelihood"
);
let ridge_norm: f64 = fit.result.mapv(|x| x * x).sum();
let ols_norm: f64 = fit_unreg.result.mapv(|x| x * x).sum();
assert!(
ridge_norm < ols_norm,
"Ridge coefficients should have smaller L2 norm"
);
Ok(())
}
#[test]
fn linear_ridge_var_weights() -> Result<()> {
let (y, x, var_wt, _freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x)
.var_weights(var_wt.clone())
.build()?;
let fit_unreg = model.fit()?;
let model = ModelBuilder::<Linear>::data(&y, &x)
.var_weights(var_wt)
.build()?;
let fit = model.fit_options().l2_reg(0.1).fit()?;
assert!(fit.n_iter <= 32, "Ridge with var weights should converge");
let ridge_norm: f64 = fit.result.mapv(|x| x * x).sum();
let ols_norm: f64 = fit_unreg.result.mapv(|x| x * x).sum();
assert!(
ridge_norm < ols_norm,
"Ridge coefficients should have smaller L2 norm"
);
Ok(())
}
#[test]
fn linear_lasso() -> Result<()> {
let (y, x, _var_wt, _freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit_unreg = model.fit()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit = model.fit_options().l1_reg(0.1).fit()?;
assert!(fit.n_iter <= 64, "Lasso should converge");
let lasso_norm: f64 = fit.result.iter().map(|x| x.abs()).sum();
let ols_norm: f64 = fit_unreg.result.iter().map(|x| x.abs()).sum();
assert!(
lasso_norm < ols_norm,
"Lasso coefficients should have smaller L1 norm"
);
Ok(())
}
#[test]
fn linear_elastic_net() -> Result<()> {
let (y, x, _var_wt, _freq_wt) = load_linear_data()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit_unreg = model.fit()?;
let model = ModelBuilder::<Linear>::data(&y, &x).build()?;
let fit = model.fit_options().l1_reg(0.05).l2_reg(0.05).fit()?;
assert!(fit.n_iter <= 64, "Elastic net should converge");
let en_l1: f64 = fit.result.iter().map(|x| x.abs()).sum();
let ols_l1: f64 = fit_unreg.result.iter().map(|x| x.abs()).sum();
assert!(
en_l1 < ols_l1,
"Elastic net coefficients should have smaller L1 norm"
);
let en_l2: f64 = fit.result.mapv(|x| x * x).sum();
let ols_l2: f64 = fit_unreg.result.mapv(|x| x * x).sum();
assert!(
en_l2 < ols_l2,
"Elastic net coefficients should have smaller L2 norm"
);
Ok(())
}