use ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use crate::error::{DmlError, Result};
mod boosting;
mod cv;
mod forest;
mod lasso;
mod logistic;
mod ols;
pub use boosting::{GradientBoostingClassifier, GradientBoostingRegressor};
pub use cv::{CvScoring, LassoCV, LogisticRegressionCV};
pub use forest::{MaxFeatures, RandomForestClassifier, RandomForestRegressor};
pub(crate) use forest::{Node, Tree, TreeBuilder, TreeParams};
pub use lasso::Lasso;
pub use logistic::LogisticRegression;
pub use ols::Ols;
pub trait Regressor: Send + Sync {
fn fit(&mut self, x: ArrayView2<'_, f64>, y: ArrayView1<'_, f64>) -> Result<()>;
fn fit_weighted(
&mut self,
_x: ArrayView2<'_, f64>,
_y: ArrayView1<'_, f64>,
_w: ArrayView1<'_, f64>,
) -> Result<()> {
Err(DmlError::InvalidArgument(
"this learner does not support sample weights".into(),
))
}
fn predict(&self, x: ArrayView2<'_, f64>) -> Result<Array1<f64>>;
fn fresh(&self) -> Box<dyn Regressor>;
}
pub trait Classifier: Send + Sync {
fn fit(&mut self, x: ArrayView2<'_, f64>, y: ArrayView1<'_, f64>) -> Result<()>;
fn predict_proba(&self, x: ArrayView2<'_, f64>) -> Result<Array1<f64>>;
fn fresh(&self) -> Box<dyn Classifier>;
}
pub(crate) struct Centered {
pub x_mean: Array1<f64>,
pub y_mean: f64,
pub xc: Array2<f64>,
pub yc: Array1<f64>,
}
pub(crate) fn center(
x: ArrayView2<'_, f64>,
y: ArrayView1<'_, f64>,
w: Option<ArrayView1<'_, f64>>,
normalize_weights: bool,
) -> Result<Centered> {
let n = x.nrows();
if y.len() != n {
return Err(DmlError::DimensionMismatch("fit x/y rows".into()));
}
if n == 0 {
return Err(DmlError::InvalidArgument("empty training set".into()));
}
let Some(w) = w else {
let x_mean = x.mean_axis(Axis(0)).expect("n > 0");
let y_mean = y.mean().expect("n > 0");
return Ok(Centered {
xc: &x - &x_mean,
yc: &y - y_mean,
x_mean,
y_mean,
});
};
if w.len() != n {
return Err(DmlError::DimensionMismatch("sample weight length".into()));
}
let total = w.sum();
if !w.iter().all(|&v| v >= 0.0 && v.is_finite()) || total <= 0.0 {
return Err(DmlError::InvalidArgument(
"sample weights must be non-negative, finite and not all zero".into(),
));
}
let w: Array1<f64> = if normalize_weights {
&w * (n as f64 / total)
} else {
w.to_owned()
};
let total = w.sum();
let x_mean = x.t().dot(&w) / total;
let y_mean = y.dot(&w) / total;
let sqrt_w = w.mapv(f64::sqrt);
let xc = (&x - &x_mean) * sqrt_w.view().insert_axis(Axis(1));
let yc = (&y - y_mean) * &sqrt_w;
Ok(Centered {
x_mean,
y_mean,
xc,
yc,
})
}