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