1use ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
5
6use crate::error::{DmlError, Result};
7
8mod boosting;
9mod cv;
10mod forest;
11mod lasso;
12mod logistic;
13mod ols;
14mod search;
15
16pub use boosting::{GradientBoostingClassifier, GradientBoostingRegressor};
17pub use cv::{CvScoring, LassoCV, LogisticRegressionCV};
18pub use forest::{MaxFeatures, RandomForestClassifier, RandomForestRegressor};
19pub(crate) use forest::{Node, Tree, TreeBuilder, TreeParams};
20pub use lasso::Lasso;
21pub use logistic::LogisticRegression;
22pub use ols::Ols;
23pub use search::{GridSearchClassifier, GridSearchRegressor};
24
25pub trait Regressor: Send + Sync {
32 fn fit(&mut self, x: ArrayView2<'_, f64>, y: ArrayView1<'_, f64>) -> Result<()>;
33
34 fn fit_weighted(
37 &mut self,
38 _x: ArrayView2<'_, f64>,
39 _y: ArrayView1<'_, f64>,
40 _w: ArrayView1<'_, f64>,
41 ) -> Result<()> {
42 Err(DmlError::InvalidArgument(
43 "this learner does not support sample weights".into(),
44 ))
45 }
46
47 fn predict(&self, x: ArrayView2<'_, f64>) -> Result<Array1<f64>>;
48 fn fresh(&self) -> Box<dyn Regressor>;
49}
50
51pub trait Classifier: Send + Sync {
54 fn fit(&mut self, x: ArrayView2<'_, f64>, y: ArrayView1<'_, f64>) -> Result<()>;
55
56 fn fit_weighted(
59 &mut self,
60 _x: ArrayView2<'_, f64>,
61 _y: ArrayView1<'_, f64>,
62 _w: ArrayView1<'_, f64>,
63 ) -> Result<()> {
64 Err(DmlError::InvalidArgument(
65 "this learner does not support sample weights".into(),
66 ))
67 }
68
69 fn predict_proba(&self, x: ArrayView2<'_, f64>) -> Result<Array1<f64>>;
70 fn fresh(&self) -> Box<dyn Classifier>;
71}
72
73pub(crate) struct Centered {
76 pub x_mean: Array1<f64>,
77 pub y_mean: f64,
78 pub xc: Array2<f64>,
79 pub yc: Array1<f64>,
80}
81
82pub(crate) fn center(
86 x: ArrayView2<'_, f64>,
87 y: ArrayView1<'_, f64>,
88 w: Option<ArrayView1<'_, f64>>,
89 normalize_weights: bool,
90) -> Result<Centered> {
91 let n = x.nrows();
92 if y.len() != n {
93 return Err(DmlError::DimensionMismatch("fit x/y rows".into()));
94 }
95 if n == 0 {
96 return Err(DmlError::InvalidArgument("empty training set".into()));
97 }
98 let Some(w) = w else {
99 let x_mean = x.mean_axis(Axis(0)).expect("n > 0");
100 let y_mean = y.mean().expect("n > 0");
101 return Ok(Centered {
102 xc: &x - &x_mean,
103 yc: &y - y_mean,
104 x_mean,
105 y_mean,
106 });
107 };
108 if w.len() != n {
109 return Err(DmlError::DimensionMismatch("sample weight length".into()));
110 }
111 let total = w.sum();
112 if !w.iter().all(|&v| v >= 0.0 && v.is_finite()) || total <= 0.0 {
113 return Err(DmlError::InvalidArgument(
114 "sample weights must be non-negative, finite and not all zero".into(),
115 ));
116 }
117 let w: Array1<f64> = if normalize_weights {
118 &w * (n as f64 / total)
119 } else {
120 w.to_owned()
121 };
122 let total = w.sum();
123 let x_mean = x.t().dot(&w) / total;
124 let y_mean = y.dot(&w) / total;
125 let sqrt_w = w.mapv(f64::sqrt);
126 let xc = (&x - &x_mean) * sqrt_w.view().insert_axis(Axis(1));
127 let yc = (&y - y_mean) * &sqrt_w;
128 Ok(Centered {
129 x_mean,
130 y_mean,
131 xc,
132 yc,
133 })
134}