1use ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
5
6use crate::error::{DmlError, Result};
7
8mod lasso;
9mod logistic;
10mod ols;
11
12pub use lasso::Lasso;
13pub use logistic::LogisticRegression;
14pub use ols::Ols;
15
16pub trait Regressor: Send + Sync {
23 fn fit(&mut self, x: ArrayView2<'_, f64>, y: ArrayView1<'_, f64>) -> Result<()>;
24
25 fn fit_weighted(
28 &mut self,
29 _x: ArrayView2<'_, f64>,
30 _y: ArrayView1<'_, f64>,
31 _w: ArrayView1<'_, f64>,
32 ) -> Result<()> {
33 Err(DmlError::InvalidArgument(
34 "this learner does not support sample weights".into(),
35 ))
36 }
37
38 fn predict(&self, x: ArrayView2<'_, f64>) -> Result<Array1<f64>>;
39 fn fresh(&self) -> Box<dyn Regressor>;
40}
41
42pub trait Classifier: Send + Sync {
45 fn fit(&mut self, x: ArrayView2<'_, f64>, y: ArrayView1<'_, f64>) -> Result<()>;
46 fn predict_proba(&self, x: ArrayView2<'_, f64>) -> Result<Array1<f64>>;
47 fn fresh(&self) -> Box<dyn Classifier>;
48}
49
50pub(crate) struct Centered {
53 pub x_mean: Array1<f64>,
54 pub y_mean: f64,
55 pub xc: Array2<f64>,
56 pub yc: Array1<f64>,
57}
58
59pub(crate) fn center(
63 x: ArrayView2<'_, f64>,
64 y: ArrayView1<'_, f64>,
65 w: Option<ArrayView1<'_, f64>>,
66 normalize_weights: bool,
67) -> Result<Centered> {
68 let n = x.nrows();
69 if y.len() != n {
70 return Err(DmlError::DimensionMismatch("fit x/y rows".into()));
71 }
72 if n == 0 {
73 return Err(DmlError::InvalidArgument("empty training set".into()));
74 }
75 let Some(w) = w else {
76 let x_mean = x.mean_axis(Axis(0)).expect("n > 0");
77 let y_mean = y.mean().expect("n > 0");
78 return Ok(Centered {
79 xc: &x - &x_mean,
80 yc: &y - y_mean,
81 x_mean,
82 y_mean,
83 });
84 };
85 if w.len() != n {
86 return Err(DmlError::DimensionMismatch("sample weight length".into()));
87 }
88 let total = w.sum();
89 if !w.iter().all(|&v| v >= 0.0 && v.is_finite()) || total <= 0.0 {
90 return Err(DmlError::InvalidArgument(
91 "sample weights must be non-negative, finite and not all zero".into(),
92 ));
93 }
94 let w: Array1<f64> = if normalize_weights {
95 &w * (n as f64 / total)
96 } else {
97 w.to_owned()
98 };
99 let total = w.sum();
100 let x_mean = x.t().dot(&w) / total;
101 let y_mean = y.dot(&w) / total;
102 let sqrt_w = w.mapv(f64::sqrt);
103 let xc = (&x - &x_mean) * sqrt_w.view().insert_axis(Axis(1));
104 let yc = (&y - y_mean) * &sqrt_w;
105 Ok(Centered {
106 x_mean,
107 y_mean,
108 xc,
109 yc,
110 })
111}