1use crate::base::SelectorMixin;
7use scirs2_core::ndarray::{Array1, Array2, Axis};
8use sklears_core::{
9 error::{Result as SklResult, SklearsError},
10 traits::{Estimator, Fit, Trained, Transform, Untrained},
11 types::Float,
12};
13use std::collections::{HashMap, HashSet};
14use std::marker::PhantomData;
15
16pub type PreprocessedDataResult = SklResult<(
18 Array2<Float>,
19 Array1<Float>,
20 Array1<Float>,
21 Array1<Float>,
22 f64,
23)>;
24
25#[derive(Debug, Clone)]
30pub struct GroupLassoSelector<State = Untrained> {
31 alpha: f64,
33 feature_groups: Vec<Vec<usize>>,
35 group_names: Option<Vec<String>>,
37 max_iter: usize,
39 tol: f64,
41 fit_intercept: bool,
43 normalize: bool,
45 state: PhantomData<State>,
46 coefficients_: Option<Array1<Float>>,
48 group_weights_: Option<Array1<Float>>,
49 selected_groups_: Option<Vec<usize>>,
50 selected_features_: Option<Vec<usize>>,
51 intercept_: Option<f64>,
52}
53
54impl GroupLassoSelector<Untrained> {
55 pub fn new() -> Self {
57 Self {
58 alpha: 1.0,
59 feature_groups: Vec::new(),
60 group_names: None,
61 max_iter: 1000,
62 tol: 1e-4,
63 fit_intercept: true,
64 normalize: true,
65 state: PhantomData,
66 coefficients_: None,
67 group_weights_: None,
68 selected_groups_: None,
69 selected_features_: None,
70 intercept_: None,
71 }
72 }
73
74 pub fn alpha(mut self, alpha: f64) -> Self {
76 self.alpha = alpha;
77 self
78 }
79
80 pub fn feature_groups(mut self, groups: Vec<Vec<usize>>) -> Self {
82 self.feature_groups = groups;
83 self
84 }
85
86 pub fn group_names(mut self, names: Vec<String>) -> Self {
88 self.group_names = Some(names);
89 self
90 }
91
92 pub fn max_iter(mut self, max_iter: usize) -> Self {
94 self.max_iter = max_iter;
95 self
96 }
97
98 pub fn tol(mut self, tol: f64) -> Self {
100 self.tol = tol;
101 self
102 }
103
104 pub fn fit_intercept(mut self, fit_intercept: bool) -> Self {
106 self.fit_intercept = fit_intercept;
107 self
108 }
109
110 pub fn normalize(mut self, normalize: bool) -> Self {
112 self.normalize = normalize;
113 self
114 }
115}
116
117impl Default for GroupLassoSelector<Untrained> {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl Estimator for GroupLassoSelector<Untrained> {
124 type Config = ();
125 type Error = SklearsError;
126 type Float = f64;
127
128 fn config(&self) -> &Self::Config {
129 &()
130 }
131}
132
133impl Fit<Array2<Float>, Array1<Float>> for GroupLassoSelector<Untrained> {
134 type Fitted = GroupLassoSelector<Trained>;
135
136 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
137 let (n_samples, n_features) = x.dim();
138
139 if n_samples != y.len() {
140 return Err(SklearsError::InvalidInput(
141 "X and y must have the same number of samples".to_string(),
142 ));
143 }
144
145 if self.feature_groups.is_empty() {
146 return Err(SklearsError::InvalidInput(
147 "Feature groups must be specified".to_string(),
148 ));
149 }
150
151 let mut all_features = HashSet::new();
153 for group in &self.feature_groups {
154 for &feature in group {
155 if feature >= n_features {
156 return Err(SklearsError::InvalidInput(
157 "Feature index out of bounds in groups".to_string(),
158 ));
159 }
160 if all_features.contains(&feature) {
161 return Err(SklearsError::InvalidInput(
162 "Feature appears in multiple groups".to_string(),
163 ));
164 }
165 all_features.insert(feature);
166 }
167 }
168
169 let (x_processed, y_processed, _feature_means, _feature_stds, y_mean) =
171 self.preprocess_data(x, y)?;
172
173 let (coefficients, group_weights, selected_groups) =
175 self.fit_group_lasso(&x_processed, &y_processed)?;
176
177 let mut selected_features = Vec::new();
179 for &group_idx in &selected_groups {
180 selected_features.extend_from_slice(&self.feature_groups[group_idx]);
181 }
182 selected_features.sort();
183
184 let intercept = if self.fit_intercept {
186 Some(y_mean)
187 } else {
188 None
189 };
190
191 Ok(GroupLassoSelector {
192 alpha: self.alpha,
193 feature_groups: self.feature_groups,
194 group_names: self.group_names,
195 max_iter: self.max_iter,
196 tol: self.tol,
197 fit_intercept: self.fit_intercept,
198 normalize: self.normalize,
199 state: PhantomData,
200 coefficients_: Some(coefficients),
201 group_weights_: Some(group_weights),
202 selected_groups_: Some(selected_groups),
203 selected_features_: Some(selected_features),
204 intercept_: intercept,
205 })
206 }
207}
208
209impl GroupLassoSelector<Untrained> {
210 fn preprocess_data(&self, x: &Array2<Float>, y: &Array1<Float>) -> PreprocessedDataResult {
211 let (n_samples, n_features) = x.dim();
212 let mut x_processed = x.clone();
213 let mut y_processed = y.clone();
214
215 let y_mean = if self.fit_intercept {
217 let mean = y.mean().unwrap_or(0.0);
218 for i in 0..n_samples {
219 y_processed[i] -= mean;
220 }
221 mean
222 } else {
223 0.0
224 };
225
226 let mut feature_means = Array1::zeros(n_features);
228 let mut feature_stds = Array1::ones(n_features);
229
230 for j in 0..n_features {
231 let col = x.column(j);
232 let mean = col.mean().unwrap_or(0.0);
233 feature_means[j] = mean;
234
235 for i in 0..n_samples {
237 x_processed[[i, j]] -= mean;
238 }
239
240 if self.normalize {
242 let std = (col.mapv(|v| (v - mean).powi(2)).sum() / n_samples as f64).sqrt();
243 if std > 1e-10 {
244 feature_stds[j] = std;
245 for i in 0..n_samples {
246 x_processed[[i, j]] /= std;
247 }
248 }
249 }
250 }
251
252 Ok((
253 x_processed,
254 y_processed,
255 feature_means,
256 feature_stds,
257 y_mean,
258 ))
259 }
260
261 fn fit_group_lasso(
262 &self,
263 x: &Array2<Float>,
264 y: &Array1<Float>,
265 ) -> SklResult<(Array1<Float>, Array1<Float>, Vec<usize>)> {
266 let (n_samples, n_features) = x.dim();
267 let n_groups = self.feature_groups.len();
268
269 let mut coefficients = Array1::zeros(n_features);
271 let mut group_weights = Array1::zeros(n_groups);
272
273 let mut group_norms = Array1::zeros(n_groups);
275 for (g, group) in self.feature_groups.iter().enumerate() {
276 let group_size = group.len() as f64;
277 group_norms[g] = group_size.sqrt();
278 }
279
280 for _iter in 0..self.max_iter {
282 let coefficients_old = coefficients.clone();
283
284 for (g, group) in self.feature_groups.iter().enumerate() {
285 let mut residual = y.clone();
287 for (h, other_group) in self.feature_groups.iter().enumerate() {
288 if g != h {
289 for &feature in other_group {
290 for i in 0..n_samples {
291 residual[i] -= x[[i, feature]] * coefficients[feature];
292 }
293 }
294 }
295 }
296
297 let mut group_gradient = Array1::zeros(group.len());
299 for (idx, &feature) in group.iter().enumerate() {
300 let mut grad = 0.0;
301 for i in 0..n_samples {
302 grad += x[[i, feature]] * residual[i];
303 }
304 group_gradient[idx] = grad / n_samples as f64;
305 }
306
307 let group_norm = group_gradient.mapv(|x| x * x).sum().sqrt();
309
310 let threshold = self.alpha * group_norms[g];
312 if group_norm > threshold {
313 let shrinkage_factor = 1.0 - threshold / group_norm;
315 group_weights[g] = group_norm * shrinkage_factor;
316
317 for (idx, &feature) in group.iter().enumerate() {
319 coefficients[feature] = group_gradient[idx] * shrinkage_factor;
320 }
321 } else {
322 group_weights[g] = 0.0;
324 for &feature in group {
325 coefficients[feature] = 0.0;
326 }
327 }
328 }
329
330 let diff = (&coefficients - &coefficients_old).mapv(|x| x.abs()).sum();
332 if diff < self.tol {
333 break;
334 }
335 }
336
337 let selected_groups: Vec<usize> = group_weights
339 .indexed_iter()
340 .filter_map(|(i, &weight)| if weight.abs() > 1e-10 { Some(i) } else { None })
341 .collect();
342
343 Ok((coefficients, group_weights, selected_groups))
344 }
345}
346
347impl Transform<Array2<Float>> for GroupLassoSelector<Trained> {
348 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
349 let selected_features = self
350 .selected_features_
351 .as_ref()
352 .expect("operation should succeed");
353 if selected_features.is_empty() {
354 return Err(SklearsError::InvalidInput(
355 "No features were selected".to_string(),
356 ));
357 }
358
359 let selected_indices: Vec<usize> = selected_features.to_vec();
360 Ok(x.select(Axis(1), &selected_indices))
361 }
362}
363
364impl SelectorMixin for GroupLassoSelector<Trained> {
365 fn get_support(&self) -> SklResult<Array1<bool>> {
366 let selected_features = self
367 .selected_features_
368 .as_ref()
369 .expect("operation should succeed");
370 let n_features = self
371 .coefficients_
372 .as_ref()
373 .expect("operation should succeed")
374 .len();
375 let mut support = Array1::from_elem(n_features, false);
376 for &idx in selected_features {
377 if idx < n_features {
378 support[idx] = true;
379 }
380 }
381 Ok(support)
382 }
383
384 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
385 let selected_features = self
386 .selected_features_
387 .as_ref()
388 .expect("operation should succeed");
389 Ok(indices
390 .iter()
391 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
392 .collect())
393 }
394}
395
396impl GroupLassoSelector<Trained> {
397 pub fn group_weights(&self) -> Option<&Array1<Float>> {
399 self.group_weights_.as_ref()
400 }
401
402 pub fn coefficients(&self) -> Option<&Array1<Float>> {
404 self.coefficients_.as_ref()
405 }
406
407 pub fn selected_groups(&self) -> Option<&Vec<usize>> {
409 self.selected_groups_.as_ref()
410 }
411
412 pub fn intercept(&self) -> Option<f64> {
414 self.intercept_
415 }
416
417 pub fn get_selected_group_names(&self) -> Vec<String> {
419 if let (Some(selected_groups), Some(group_names)) =
420 (&self.selected_groups_, &self.group_names)
421 {
422 selected_groups
423 .iter()
424 .filter_map(|&idx| group_names.get(idx).cloned())
425 .collect()
426 } else {
427 self.selected_groups_
428 .as_ref()
429 .unwrap_or(&Vec::new())
430 .iter()
431 .map(|&idx| format!("Group_{}", idx))
432 .collect()
433 }
434 }
435
436 pub fn predict(&self, x: &Array2<Float>) -> SklResult<Array1<Float>> {
438 let coefficients = self
439 .coefficients_
440 .as_ref()
441 .expect("operation should succeed");
442 let (n_samples, n_features) = x.dim();
443
444 if n_features != coefficients.len() {
445 return Err(SklearsError::InvalidInput(
446 "X has wrong number of features".to_string(),
447 ));
448 }
449
450 let mut predictions = Array1::zeros(n_samples);
451
452 for i in 0..n_samples {
453 let mut pred = 0.0;
454 for j in 0..n_features {
455 pred += x[[i, j]] * coefficients[j];
456 }
457
458 if let Some(intercept) = self.intercept_ {
459 pred += intercept;
460 }
461
462 predictions[i] = pred;
463 }
464
465 Ok(predictions)
466 }
467}
468
469#[derive(Debug, Clone)]
474pub struct SparseGroupLassoSelector<State = Untrained> {
475 alpha_group: f64,
477 alpha_element: f64,
479 feature_groups: Vec<Vec<usize>>,
481 group_names: Option<Vec<String>>,
483 max_iter: usize,
485 tol: f64,
487 fit_intercept: bool,
489 normalize: bool,
491 state: PhantomData<State>,
492 coefficients_: Option<Array1<Float>>,
494 selected_features_: Option<Vec<usize>>,
495 _selected_groups_: Option<Vec<usize>>,
496 _intercept_: Option<f64>,
497}
498
499impl SparseGroupLassoSelector<Untrained> {
500 pub fn new() -> Self {
502 Self {
503 alpha_group: 1.0,
504 alpha_element: 1.0,
505 feature_groups: Vec::new(),
506 group_names: None,
507 max_iter: 1000,
508 tol: 1e-4,
509 fit_intercept: true,
510 normalize: true,
511 state: PhantomData,
512 coefficients_: None,
513 selected_features_: None,
514 _selected_groups_: None,
515 _intercept_: None,
516 }
517 }
518
519 pub fn alpha_group(mut self, alpha: f64) -> Self {
521 self.alpha_group = alpha;
522 self
523 }
524
525 pub fn alpha_element(mut self, alpha: f64) -> Self {
527 self.alpha_element = alpha;
528 self
529 }
530
531 pub fn feature_groups(mut self, groups: Vec<Vec<usize>>) -> Self {
533 self.feature_groups = groups;
534 self
535 }
536
537 pub fn group_names(mut self, names: Vec<String>) -> Self {
539 self.group_names = Some(names);
540 self
541 }
542}
543
544impl Default for SparseGroupLassoSelector<Untrained> {
545 fn default() -> Self {
546 Self::new()
547 }
548}
549
550impl Estimator for SparseGroupLassoSelector<Untrained> {
551 type Config = ();
552 type Error = SklearsError;
553 type Float = f64;
554
555 fn config(&self) -> &Self::Config {
556 &()
557 }
558}
559
560impl Fit<Array2<Float>, Array1<Float>> for SparseGroupLassoSelector<Untrained> {
561 type Fitted = SparseGroupLassoSelector<Trained>;
562
563 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
564 let (n_samples, _n_features) = x.dim();
568 if n_samples != y.len() {
569 return Err(SklearsError::InvalidInput(
570 "X and y must have the same number of samples".to_string(),
571 ));
572 }
573
574 if self.feature_groups.is_empty() {
575 return Err(SklearsError::InvalidInput(
576 "Feature groups must be specified".to_string(),
577 ));
578 }
579
580 let group_lasso = GroupLassoSelector::new()
582 .alpha(self.alpha_group)
583 .feature_groups(self.feature_groups.clone())
584 .fit_intercept(self.fit_intercept)
585 .normalize(self.normalize);
586
587 let fitted_group_lasso = group_lasso.fit(x, y)?;
588 let mut coefficients = fitted_group_lasso
589 .coefficients()
590 .expect("operation should succeed")
591 .clone();
592
593 for coeff in coefficients.iter_mut() {
595 *coeff = soft_threshold(*coeff, self.alpha_element);
596 }
597
598 let selected_features: Vec<usize> = coefficients
600 .indexed_iter()
601 .filter_map(|(i, &coeff)| if coeff.abs() > 1e-10 { Some(i) } else { None })
602 .collect();
603
604 let mut selected_groups = Vec::new();
605 for (g, group) in self.feature_groups.iter().enumerate() {
606 if group.iter().any(|&f| selected_features.contains(&f)) {
607 selected_groups.push(g);
608 }
609 }
610
611 Ok(SparseGroupLassoSelector {
612 alpha_group: self.alpha_group,
613 alpha_element: self.alpha_element,
614 feature_groups: self.feature_groups,
615 group_names: self.group_names,
616 max_iter: self.max_iter,
617 tol: self.tol,
618 fit_intercept: self.fit_intercept,
619 normalize: self.normalize,
620 state: PhantomData,
621 coefficients_: Some(coefficients),
622 selected_features_: Some(selected_features),
623 _selected_groups_: Some(selected_groups),
624 _intercept_: fitted_group_lasso.intercept(),
625 })
626 }
627}
628
629impl Transform<Array2<Float>> for SparseGroupLassoSelector<Trained> {
630 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
631 let selected_features = self
632 .selected_features_
633 .as_ref()
634 .expect("operation should succeed");
635 if selected_features.is_empty() {
636 return Err(SklearsError::InvalidInput(
637 "No features were selected".to_string(),
638 ));
639 }
640
641 let selected_indices: Vec<usize> = selected_features.to_vec();
642 Ok(x.select(Axis(1), &selected_indices))
643 }
644}
645
646impl SelectorMixin for SparseGroupLassoSelector<Trained> {
647 fn get_support(&self) -> SklResult<Array1<bool>> {
648 let selected_features = self
649 .selected_features_
650 .as_ref()
651 .expect("operation should succeed");
652 let n_features = self
653 .coefficients_
654 .as_ref()
655 .expect("operation should succeed")
656 .len();
657 let mut support = Array1::from_elem(n_features, false);
658 for &idx in selected_features {
659 if idx < n_features {
660 support[idx] = true;
661 }
662 }
663 Ok(support)
664 }
665
666 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
667 let selected_features = self
668 .selected_features_
669 .as_ref()
670 .expect("operation should succeed");
671 Ok(indices
672 .iter()
673 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
674 .collect())
675 }
676}
677
678fn soft_threshold(x: f64, lambda: f64) -> f64 {
680 if x > lambda {
681 x - lambda
682 } else if x < -lambda {
683 x + lambda
684 } else {
685 0.0
686 }
687}
688
689#[derive(Debug, Clone)]
694pub struct HierarchicalStructuredSparsitySelector<State = Untrained> {
695 alpha: f64,
697 hierarchy: HashMap<usize, Vec<usize>>,
699 feature_to_node: HashMap<usize, usize>,
701 max_iter: usize,
703 tol: f64,
705 fit_intercept: bool,
707 normalize: bool,
709 state: PhantomData<State>,
710 coefficients_: Option<Array1<Float>>,
712 selected_features_: Option<Vec<usize>>,
713 _selected_nodes_: Option<Vec<usize>>,
714 _intercept_: Option<f64>,
715}
716
717impl HierarchicalStructuredSparsitySelector<Untrained> {
718 pub fn new() -> Self {
720 Self {
721 alpha: 1.0,
722 hierarchy: HashMap::new(),
723 feature_to_node: HashMap::new(),
724 max_iter: 1000,
725 tol: 1e-4,
726 fit_intercept: true,
727 normalize: true,
728 state: PhantomData,
729 coefficients_: None,
730 selected_features_: None,
731 _selected_nodes_: None,
732 _intercept_: None,
733 }
734 }
735
736 pub fn alpha(mut self, alpha: f64) -> Self {
738 self.alpha = alpha;
739 self
740 }
741
742 pub fn hierarchy(mut self, hierarchy: HashMap<usize, Vec<usize>>) -> Self {
744 self.hierarchy = hierarchy;
745 self
746 }
747
748 pub fn feature_to_node(mut self, mapping: HashMap<usize, usize>) -> Self {
750 self.feature_to_node = mapping;
751 self
752 }
753
754 pub fn max_iter(mut self, max_iter: usize) -> Self {
756 self.max_iter = max_iter;
757 self
758 }
759
760 pub fn tol(mut self, tol: f64) -> Self {
762 self.tol = tol;
763 self
764 }
765
766 pub fn fit_intercept(mut self, fit_intercept: bool) -> Self {
768 self.fit_intercept = fit_intercept;
769 self
770 }
771
772 pub fn normalize(mut self, normalize: bool) -> Self {
774 self.normalize = normalize;
775 self
776 }
777}
778
779impl Default for HierarchicalStructuredSparsitySelector<Untrained> {
780 fn default() -> Self {
781 Self::new()
782 }
783}
784
785impl Estimator for HierarchicalStructuredSparsitySelector<Untrained> {
786 type Config = ();
787 type Error = SklearsError;
788 type Float = f64;
789
790 fn config(&self) -> &Self::Config {
791 &()
792 }
793}
794
795impl Fit<Array2<Float>, Array1<Float>> for HierarchicalStructuredSparsitySelector<Untrained> {
796 type Fitted = HierarchicalStructuredSparsitySelector<Trained>;
797
798 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
799 let (n_samples, _n_features) = x.dim();
800
801 if n_samples != y.len() {
802 return Err(SklearsError::InvalidInput(
803 "X and y must have the same number of samples".to_string(),
804 ));
805 }
806
807 if self.hierarchy.is_empty() || self.feature_to_node.is_empty() {
808 return Err(SklearsError::InvalidInput(
809 "Hierarchical structure and feature mapping must be specified".to_string(),
810 ));
811 }
812
813 let (x_processed, y_processed, _, _, y_mean) = self.preprocess_data(x, y)?;
815
816 let (coefficients, selected_nodes) =
818 self.fit_hierarchical_sparsity(&x_processed, &y_processed)?;
819
820 let selected_features: Vec<usize> = self
822 .feature_to_node
823 .iter()
824 .filter_map(|(&feature, &node)| {
825 if selected_nodes.contains(&node) {
826 Some(feature)
827 } else {
828 None
829 }
830 })
831 .collect();
832
833 let intercept = if self.fit_intercept {
834 Some(y_mean)
835 } else {
836 None
837 };
838
839 Ok(HierarchicalStructuredSparsitySelector {
840 alpha: self.alpha,
841 hierarchy: self.hierarchy,
842 feature_to_node: self.feature_to_node,
843 max_iter: self.max_iter,
844 tol: self.tol,
845 fit_intercept: self.fit_intercept,
846 normalize: self.normalize,
847 state: PhantomData,
848 coefficients_: Some(coefficients),
849 selected_features_: Some(selected_features),
850 _selected_nodes_: Some(selected_nodes),
851 _intercept_: intercept,
852 })
853 }
854}
855
856impl HierarchicalStructuredSparsitySelector<Untrained> {
857 fn preprocess_data(&self, x: &Array2<Float>, y: &Array1<Float>) -> PreprocessedDataResult {
858 let (n_samples, n_features) = x.dim();
859 let mut x_processed = x.clone();
860 let mut y_processed = y.clone();
861
862 let y_mean = if self.fit_intercept {
864 let mean = y.mean().unwrap_or(0.0);
865 for i in 0..n_samples {
866 y_processed[i] -= mean;
867 }
868 mean
869 } else {
870 0.0
871 };
872
873 let mut feature_means = Array1::zeros(n_features);
875 let mut feature_stds = Array1::ones(n_features);
876
877 for j in 0..n_features {
878 let col = x.column(j);
879 let mean = col.mean().unwrap_or(0.0);
880 feature_means[j] = mean;
881
882 for i in 0..n_samples {
884 x_processed[[i, j]] -= mean;
885 }
886
887 if self.normalize {
889 let std = (col.mapv(|v| (v - mean).powi(2)).sum() / n_samples as f64).sqrt();
890 if std > 1e-10 {
891 feature_stds[j] = std;
892 for i in 0..n_samples {
893 x_processed[[i, j]] /= std;
894 }
895 }
896 }
897 }
898
899 Ok((
900 x_processed,
901 y_processed,
902 feature_means,
903 feature_stds,
904 y_mean,
905 ))
906 }
907
908 fn fit_hierarchical_sparsity(
909 &self,
910 x: &Array2<Float>,
911 y: &Array1<Float>,
912 ) -> SklResult<(Array1<Float>, Vec<usize>)> {
913 let (n_samples, n_features) = x.dim();
914 let mut coefficients = Array1::zeros(n_features);
915 let step_size = 0.01;
916
917 for _iter in 0..self.max_iter {
918 let coefficients_old = coefficients.clone();
919
920 let mut gradient = Array1::<f64>::zeros(n_features);
922 for i in 0..n_samples {
923 let residual = y[i] - x.row(i).dot(&coefficients);
924 for j in 0..n_features {
925 gradient[j] -= x[[i, j]] * residual / n_samples as f64;
926 }
927 }
928
929 for j in 0..n_features {
931 coefficients[j] -= step_size * gradient[j];
932 }
933
934 self.hierarchical_proximal_operator(&mut coefficients)?;
936
937 let diff = (&coefficients - &coefficients_old).mapv(|x| x.abs()).sum();
939 if diff < self.tol {
940 break;
941 }
942 }
943
944 let selected_nodes = self.get_selected_nodes(&coefficients);
946
947 Ok((coefficients, selected_nodes))
948 }
949
950 fn hierarchical_proximal_operator(&self, coefficients: &mut Array1<Float>) -> SklResult<()> {
951 for (&parent, children) in &self.hierarchy {
953 let parent_features: Vec<usize> = self
954 .feature_to_node
955 .iter()
956 .filter_map(|(&feature, &node)| if node == parent { Some(feature) } else { None })
957 .collect();
958
959 let parent_norm = parent_features
960 .iter()
961 .map(|&f| coefficients[f] * coefficients[f])
962 .sum::<f64>()
963 .sqrt();
964
965 let threshold = self.alpha * (parent_features.len() as f64).sqrt();
967 if parent_norm <= threshold {
968 for &feature in &parent_features {
970 coefficients[feature] = 0.0;
971 }
972
973 self.zero_out_children(coefficients, children)?;
975 } else {
976 let shrinkage = 1.0 - threshold / parent_norm;
978 for &feature in &parent_features {
979 coefficients[feature] *= shrinkage;
980 }
981 }
982 }
983
984 Ok(())
985 }
986
987 fn zero_out_children(
988 &self,
989 coefficients: &mut Array1<Float>,
990 children: &[usize],
991 ) -> SklResult<()> {
992 for &child in children {
993 let child_features: Vec<usize> = self
994 .feature_to_node
995 .iter()
996 .filter_map(|(&feature, &node)| if node == child { Some(feature) } else { None })
997 .collect();
998
999 for &feature in &child_features {
1000 coefficients[feature] = 0.0;
1001 }
1002
1003 if let Some(grandchildren) = self.hierarchy.get(&child) {
1005 self.zero_out_children(coefficients, grandchildren)?;
1006 }
1007 }
1008 Ok(())
1009 }
1010
1011 fn get_selected_nodes(&self, coefficients: &Array1<Float>) -> Vec<usize> {
1012 let mut selected_nodes = HashSet::new();
1013
1014 for (&feature, &node) in &self.feature_to_node {
1015 if coefficients[feature].abs() > 1e-10 {
1016 selected_nodes.insert(node);
1017 }
1018 }
1019
1020 selected_nodes.into_iter().collect()
1021 }
1022}
1023
1024impl Transform<Array2<Float>> for HierarchicalStructuredSparsitySelector<Trained> {
1025 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
1026 let selected_features = self
1027 .selected_features_
1028 .as_ref()
1029 .expect("operation should succeed");
1030 if selected_features.is_empty() {
1031 return Err(SklearsError::InvalidInput(
1032 "No features were selected".to_string(),
1033 ));
1034 }
1035
1036 let selected_indices: Vec<usize> = selected_features.to_vec();
1037 Ok(x.select(Axis(1), &selected_indices))
1038 }
1039}
1040
1041impl SelectorMixin for HierarchicalStructuredSparsitySelector<Trained> {
1042 fn get_support(&self) -> SklResult<Array1<bool>> {
1043 let selected_features = self
1044 .selected_features_
1045 .as_ref()
1046 .expect("operation should succeed");
1047 let n_features = self
1048 .coefficients_
1049 .as_ref()
1050 .expect("operation should succeed")
1051 .len();
1052 let mut support = Array1::from_elem(n_features, false);
1053 for &idx in selected_features {
1054 if idx < n_features {
1055 support[idx] = true;
1056 }
1057 }
1058 Ok(support)
1059 }
1060
1061 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
1062 let selected_features = self
1063 .selected_features_
1064 .as_ref()
1065 .expect("operation should succeed");
1066 Ok(indices
1067 .iter()
1068 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
1069 .collect())
1070 }
1071}
1072
1073#[derive(Debug, Clone)]
1078pub struct GraphStructuredSparsitySelector<State = Untrained> {
1079 alpha: f64,
1081 adjacency_matrix: Array2<Float>,
1083 max_iter: usize,
1085 tol: f64,
1087 fit_intercept: bool,
1089 normalize: bool,
1091 state: PhantomData<State>,
1092 coefficients_: Option<Array1<Float>>,
1094 selected_features_: Option<Vec<usize>>,
1095 _intercept_: Option<f64>,
1096}
1097
1098impl GraphStructuredSparsitySelector<Untrained> {
1099 pub fn new() -> Self {
1101 Self {
1102 alpha: 1.0,
1103 adjacency_matrix: Array2::zeros((0, 0)),
1104 max_iter: 1000,
1105 tol: 1e-4,
1106 fit_intercept: true,
1107 normalize: true,
1108 state: PhantomData,
1109 coefficients_: None,
1110 selected_features_: None,
1111 _intercept_: None,
1112 }
1113 }
1114
1115 pub fn alpha(mut self, alpha: f64) -> Self {
1117 self.alpha = alpha;
1118 self
1119 }
1120
1121 pub fn adjacency_matrix(mut self, matrix: Array2<Float>) -> Self {
1123 self.adjacency_matrix = matrix;
1124 self
1125 }
1126
1127 pub fn max_iter(mut self, max_iter: usize) -> Self {
1129 self.max_iter = max_iter;
1130 self
1131 }
1132
1133 pub fn tol(mut self, tol: f64) -> Self {
1135 self.tol = tol;
1136 self
1137 }
1138
1139 pub fn fit_intercept(mut self, fit_intercept: bool) -> Self {
1141 self.fit_intercept = fit_intercept;
1142 self
1143 }
1144
1145 pub fn normalize(mut self, normalize: bool) -> Self {
1147 self.normalize = normalize;
1148 self
1149 }
1150}
1151
1152impl Default for GraphStructuredSparsitySelector<Untrained> {
1153 fn default() -> Self {
1154 Self::new()
1155 }
1156}
1157
1158impl Estimator for GraphStructuredSparsitySelector<Untrained> {
1159 type Config = ();
1160 type Error = SklearsError;
1161 type Float = f64;
1162
1163 fn config(&self) -> &Self::Config {
1164 &()
1165 }
1166}
1167
1168impl Fit<Array2<Float>, Array1<Float>> for GraphStructuredSparsitySelector<Untrained> {
1169 type Fitted = GraphStructuredSparsitySelector<Trained>;
1170
1171 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
1172 let (n_samples, n_features) = x.dim();
1173
1174 if n_samples != y.len() {
1175 return Err(SklearsError::InvalidInput(
1176 "X and y must have the same number of samples".to_string(),
1177 ));
1178 }
1179
1180 if self.adjacency_matrix.nrows() != n_features
1181 || self.adjacency_matrix.ncols() != n_features
1182 {
1183 return Err(SklearsError::InvalidInput(
1184 "Adjacency matrix dimensions must match number of features".to_string(),
1185 ));
1186 }
1187
1188 let laplacian = self.compute_graph_laplacian()?;
1190
1191 let (coefficients, y_mean) = self.fit_graph_sparsity(x, y, &laplacian)?;
1193
1194 let selected_features: Vec<usize> = coefficients
1196 .indexed_iter()
1197 .filter_map(|(i, &coeff)| if coeff.abs() > 1e-10 { Some(i) } else { None })
1198 .collect();
1199
1200 let intercept = if self.fit_intercept {
1201 Some(y_mean)
1202 } else {
1203 None
1204 };
1205
1206 Ok(GraphStructuredSparsitySelector {
1207 alpha: self.alpha,
1208 adjacency_matrix: self.adjacency_matrix,
1209 max_iter: self.max_iter,
1210 tol: self.tol,
1211 fit_intercept: self.fit_intercept,
1212 normalize: self.normalize,
1213 state: PhantomData,
1214 coefficients_: Some(coefficients),
1215 selected_features_: Some(selected_features),
1216 _intercept_: intercept,
1217 })
1218 }
1219}
1220
1221impl GraphStructuredSparsitySelector<Untrained> {
1222 fn compute_graph_laplacian(&self) -> SklResult<Array2<Float>> {
1223 let n_features = self.adjacency_matrix.nrows();
1224 let mut laplacian = Array2::zeros((n_features, n_features));
1225
1226 let mut degrees = Array1::zeros(n_features);
1228 for i in 0..n_features {
1229 degrees[i] = self.adjacency_matrix.row(i).sum();
1230 }
1231
1232 for i in 0..n_features {
1234 laplacian[[i, i]] = degrees[i];
1235 for j in 0..n_features {
1236 if i != j {
1237 laplacian[[i, j]] = -self.adjacency_matrix[[i, j]];
1238 }
1239 }
1240 }
1241
1242 Ok(laplacian)
1243 }
1244
1245 fn fit_graph_sparsity(
1246 &self,
1247 x: &Array2<Float>,
1248 y: &Array1<Float>,
1249 laplacian: &Array2<Float>,
1250 ) -> SklResult<(Array1<Float>, f64)> {
1251 let (n_samples, n_features) = x.dim();
1252 let mut coefficients = Array1::zeros(n_features);
1253 let step_size = 0.01;
1254
1255 let y_mean = if self.fit_intercept {
1257 y.mean().unwrap_or(0.0)
1258 } else {
1259 0.0
1260 };
1261
1262 let mut y_centered = y.clone();
1263 if self.fit_intercept {
1264 for i in 0..n_samples {
1265 y_centered[i] -= y_mean;
1266 }
1267 }
1268
1269 for _iter in 0..self.max_iter {
1270 let coefficients_old = coefficients.clone();
1271
1272 let mut gradient = Array1::<f64>::zeros(n_features);
1274 for i in 0..n_samples {
1275 let residual = y_centered[i] - x.row(i).dot(&coefficients);
1276 for j in 0..n_features {
1277 gradient[j] -= x[[i, j]] * residual / n_samples as f64;
1278 }
1279 }
1280
1281 let graph_penalty_grad = laplacian.dot(&coefficients) * self.alpha;
1283 gradient = gradient + graph_penalty_grad;
1284
1285 for j in 0..n_features {
1287 coefficients[j] -= step_size * gradient[j];
1288 }
1289
1290 for coeff in coefficients.iter_mut() {
1292 *coeff = soft_threshold(*coeff, step_size * self.alpha * 0.1);
1293 }
1294
1295 let diff = (&coefficients - &coefficients_old).mapv(|x| x.abs()).sum();
1297 if diff < self.tol {
1298 break;
1299 }
1300 }
1301
1302 Ok((coefficients, y_mean))
1303 }
1304}
1305
1306impl Transform<Array2<Float>> for GraphStructuredSparsitySelector<Trained> {
1307 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
1308 let selected_features = self
1309 .selected_features_
1310 .as_ref()
1311 .expect("operation should succeed");
1312 if selected_features.is_empty() {
1313 return Err(SklearsError::InvalidInput(
1314 "No features were selected".to_string(),
1315 ));
1316 }
1317
1318 let selected_indices: Vec<usize> = selected_features.to_vec();
1319 Ok(x.select(Axis(1), &selected_indices))
1320 }
1321}
1322
1323impl SelectorMixin for GraphStructuredSparsitySelector<Trained> {
1324 fn get_support(&self) -> SklResult<Array1<bool>> {
1325 let selected_features = self
1326 .selected_features_
1327 .as_ref()
1328 .expect("operation should succeed");
1329 let n_features = self
1330 .coefficients_
1331 .as_ref()
1332 .expect("operation should succeed")
1333 .len();
1334 let mut support = Array1::from_elem(n_features, false);
1335 for &idx in selected_features {
1336 if idx < n_features {
1337 support[idx] = true;
1338 }
1339 }
1340 Ok(support)
1341 }
1342
1343 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
1344 let selected_features = self
1345 .selected_features_
1346 .as_ref()
1347 .expect("operation should succeed");
1348 Ok(indices
1349 .iter()
1350 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
1351 .collect())
1352 }
1353}
1354
1355#[derive(Debug, Clone)]
1361pub struct OverlappingGroupSparsitySelector<State = Untrained> {
1362 alpha: f64,
1364 overlapping_groups: Vec<Vec<usize>>,
1366 group_names: Option<Vec<String>>,
1368 max_iter: usize,
1370 tol: f64,
1372 fit_intercept: bool,
1374 normalize: bool,
1376 state: PhantomData<State>,
1377 coefficients_: Option<Array1<Float>>,
1379 selected_features_: Option<Vec<usize>>,
1380 _selected_groups_: Option<Vec<usize>>,
1381 _intercept_: Option<f64>,
1382}
1383
1384impl OverlappingGroupSparsitySelector<Untrained> {
1385 pub fn new() -> Self {
1387 Self {
1388 alpha: 1.0,
1389 overlapping_groups: Vec::new(),
1390 group_names: None,
1391 max_iter: 1000,
1392 tol: 1e-4,
1393 fit_intercept: true,
1394 normalize: true,
1395 state: PhantomData,
1396 coefficients_: None,
1397 selected_features_: None,
1398 _selected_groups_: None,
1399 _intercept_: None,
1400 }
1401 }
1402
1403 pub fn alpha(mut self, alpha: f64) -> Self {
1405 self.alpha = alpha;
1406 self
1407 }
1408
1409 pub fn overlapping_groups(mut self, groups: Vec<Vec<usize>>) -> Self {
1411 self.overlapping_groups = groups;
1412 self
1413 }
1414
1415 pub fn group_names(mut self, names: Vec<String>) -> Self {
1417 self.group_names = Some(names);
1418 self
1419 }
1420
1421 pub fn max_iter(mut self, max_iter: usize) -> Self {
1423 self.max_iter = max_iter;
1424 self
1425 }
1426
1427 pub fn tol(mut self, tol: f64) -> Self {
1429 self.tol = tol;
1430 self
1431 }
1432
1433 pub fn fit_intercept(mut self, fit_intercept: bool) -> Self {
1435 self.fit_intercept = fit_intercept;
1436 self
1437 }
1438
1439 pub fn normalize(mut self, normalize: bool) -> Self {
1441 self.normalize = normalize;
1442 self
1443 }
1444}
1445
1446impl Default for OverlappingGroupSparsitySelector<Untrained> {
1447 fn default() -> Self {
1448 Self::new()
1449 }
1450}
1451
1452impl Estimator for OverlappingGroupSparsitySelector<Untrained> {
1453 type Config = ();
1454 type Error = SklearsError;
1455 type Float = f64;
1456
1457 fn config(&self) -> &Self::Config {
1458 &()
1459 }
1460}
1461
1462impl Fit<Array2<Float>, Array1<Float>> for OverlappingGroupSparsitySelector<Untrained> {
1463 type Fitted = OverlappingGroupSparsitySelector<Trained>;
1464
1465 fn fit(self, x: &Array2<Float>, y: &Array1<Float>) -> SklResult<Self::Fitted> {
1466 let (n_samples, n_features) = x.dim();
1467
1468 if n_samples != y.len() {
1469 return Err(SklearsError::InvalidInput(
1470 "X and y must have the same number of samples".to_string(),
1471 ));
1472 }
1473
1474 if self.overlapping_groups.is_empty() {
1475 return Err(SklearsError::InvalidInput(
1476 "Overlapping groups must be specified".to_string(),
1477 ));
1478 }
1479
1480 for group in &self.overlapping_groups {
1482 for &feature in group {
1483 if feature >= n_features {
1484 return Err(SklearsError::InvalidInput(
1485 "Feature index out of bounds in groups".to_string(),
1486 ));
1487 }
1488 }
1489 }
1490
1491 let (coefficients, selected_groups, y_mean) = self.fit_overlapping_groups(x, y)?;
1493
1494 let mut selected_features = HashSet::new();
1496 for &group_idx in &selected_groups {
1497 for &feature in &self.overlapping_groups[group_idx] {
1498 selected_features.insert(feature);
1499 }
1500 }
1501 let selected_features: Vec<usize> = selected_features.into_iter().collect();
1502
1503 let intercept = if self.fit_intercept {
1504 Some(y_mean)
1505 } else {
1506 None
1507 };
1508
1509 Ok(OverlappingGroupSparsitySelector {
1510 alpha: self.alpha,
1511 overlapping_groups: self.overlapping_groups,
1512 group_names: self.group_names,
1513 max_iter: self.max_iter,
1514 tol: self.tol,
1515 fit_intercept: self.fit_intercept,
1516 normalize: self.normalize,
1517 state: PhantomData,
1518 coefficients_: Some(coefficients),
1519 selected_features_: Some(selected_features),
1520 _selected_groups_: Some(selected_groups),
1521 _intercept_: intercept,
1522 })
1523 }
1524}
1525
1526impl OverlappingGroupSparsitySelector<Untrained> {
1527 fn fit_overlapping_groups(
1528 &self,
1529 x: &Array2<Float>,
1530 y: &Array1<Float>,
1531 ) -> SklResult<(Array1<Float>, Vec<usize>, f64)> {
1532 let (n_samples, n_features) = x.dim();
1533 let n_groups = self.overlapping_groups.len();
1534 let mut coefficients = Array1::zeros(n_features);
1535 let step_size = 0.01;
1536
1537 let y_mean = if self.fit_intercept {
1539 y.mean().unwrap_or(0.0)
1540 } else {
1541 0.0
1542 };
1543
1544 let mut y_centered = y.clone();
1545 if self.fit_intercept {
1546 for i in 0..n_samples {
1547 y_centered[i] -= y_mean;
1548 }
1549 }
1550
1551 let mut overlap_matrix = Array2::zeros((n_features, n_groups));
1553 for (g, group) in self.overlapping_groups.iter().enumerate() {
1554 for &feature in group {
1555 overlap_matrix[[feature, g]] = 1.0;
1556 }
1557 }
1558
1559 let mut overlap_weights = Array1::zeros(n_features);
1561 for i in 0..n_features {
1562 overlap_weights[i] = overlap_matrix.row(i).sum();
1563 if overlap_weights[i] == 0.0 {
1564 overlap_weights[i] = 1.0; }
1566 }
1567
1568 for _iter in 0..self.max_iter {
1569 let coefficients_old = coefficients.clone();
1570
1571 let mut gradient = Array1::<f64>::zeros(n_features);
1573 for i in 0..n_samples {
1574 let residual = y_centered[i] - x.row(i).dot(&coefficients);
1575 for j in 0..n_features {
1576 gradient[j] -= x[[i, j]] * residual / n_samples as f64;
1577 }
1578 }
1579
1580 for j in 0..n_features {
1582 coefficients[j] -= step_size * gradient[j];
1583 }
1584
1585 for group in self.overlapping_groups.iter() {
1587 let group_norm = group
1589 .iter()
1590 .map(|&f| coefficients[f] * coefficients[f])
1591 .sum::<f64>()
1592 .sqrt();
1593
1594 let group_size = group.len() as f64;
1596 let threshold = self.alpha * group_size.sqrt();
1597
1598 if group_norm > threshold {
1599 let shrinkage_factor = 1.0 - threshold / group_norm;
1600 for &feature in group {
1601 let adjustment = shrinkage_factor / overlap_weights[feature];
1603 coefficients[feature] = coefficients[feature] * (1.0 - adjustment)
1604 + coefficients[feature] * shrinkage_factor * adjustment;
1605 }
1606 }
1607 }
1608
1609 let diff = (&coefficients - &coefficients_old).mapv(|x| x.abs()).sum();
1611 if diff < self.tol {
1612 break;
1613 }
1614 }
1615
1616 let selected_groups: Vec<usize> = (0..n_groups)
1618 .filter(|&g| {
1619 let group_norm = self.overlapping_groups[g]
1620 .iter()
1621 .map(|&f| coefficients[f] * coefficients[f])
1622 .sum::<f64>()
1623 .sqrt();
1624 group_norm > 1e-10
1625 })
1626 .collect();
1627
1628 Ok((coefficients, selected_groups, y_mean))
1629 }
1630}
1631
1632impl Transform<Array2<Float>> for OverlappingGroupSparsitySelector<Trained> {
1633 fn transform(&self, x: &Array2<Float>) -> SklResult<Array2<Float>> {
1634 let selected_features = self
1635 .selected_features_
1636 .as_ref()
1637 .expect("operation should succeed");
1638 if selected_features.is_empty() {
1639 return Err(SklearsError::InvalidInput(
1640 "No features were selected".to_string(),
1641 ));
1642 }
1643
1644 let selected_indices: Vec<usize> = selected_features.to_vec();
1645 Ok(x.select(Axis(1), &selected_indices))
1646 }
1647}
1648
1649impl SelectorMixin for OverlappingGroupSparsitySelector<Trained> {
1650 fn get_support(&self) -> SklResult<Array1<bool>> {
1651 let selected_features = self
1652 .selected_features_
1653 .as_ref()
1654 .expect("operation should succeed");
1655 let n_features = self
1656 .coefficients_
1657 .as_ref()
1658 .expect("operation should succeed")
1659 .len();
1660 let mut support = Array1::from_elem(n_features, false);
1661 for &idx in selected_features {
1662 if idx < n_features {
1663 support[idx] = true;
1664 }
1665 }
1666 Ok(support)
1667 }
1668
1669 fn transform_features(&self, indices: &[usize]) -> SklResult<Vec<usize>> {
1670 let selected_features = self
1671 .selected_features_
1672 .as_ref()
1673 .expect("operation should succeed");
1674 Ok(indices
1675 .iter()
1676 .filter_map(|&idx| selected_features.iter().position(|&f| f == idx))
1677 .collect())
1678 }
1679}
1680
1681#[allow(non_snake_case)]
1682#[cfg(test)]
1683mod tests {
1684 use super::*;
1685 use scirs2_core::ndarray::array;
1686
1687 #[test]
1688 fn test_group_lasso_basic() {
1689 let x = array![
1690 [1.0, 0.0, 2.0, 0.0],
1691 [0.0, 1.0, 0.0, 3.0],
1692 [2.0, 0.0, 4.0, 0.0],
1693 [0.0, 2.0, 0.0, 6.0],
1694 [3.0, 0.0, 6.0, 0.0],
1695 ];
1696 let y = array![1.0, 2.0, 2.0, 4.0, 3.0];
1697
1698 let groups = vec![vec![0, 2], vec![1, 3]];
1700
1701 let selector = GroupLassoSelector::new().alpha(0.1).feature_groups(groups);
1702
1703 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1704
1705 assert!(fitted_selector.coefficients().is_some());
1706 assert!(fitted_selector.selected_groups().is_some());
1707 assert!(!fitted_selector
1708 .selected_groups()
1709 .expect("operation should succeed")
1710 .is_empty());
1711 }
1712
1713 #[test]
1714 fn test_group_lasso_high_regularization() {
1715 let x = array![[1.0, 2.0, 3.0], [2.0, 3.0, 4.0], [3.0, 4.0, 5.0],];
1716 let y = array![1.0, 2.0, 3.0];
1717
1718 let groups = vec![vec![0], vec![1], vec![2]];
1719 let n_groups = groups.len();
1720
1721 let selector = GroupLassoSelector::new()
1722 .alpha(10.0) .feature_groups(groups);
1724
1725 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1726
1727 let selected_groups = fitted_selector
1729 .selected_groups()
1730 .expect("operation should succeed");
1731 assert!(selected_groups.len() <= n_groups);
1732 }
1733
1734 #[test]
1735 fn test_sparse_group_lasso() {
1736 let x = array![
1737 [1.0, 0.0, 2.0, 0.0],
1738 [0.0, 1.0, 0.0, 3.0],
1739 [2.0, 0.0, 4.0, 0.0],
1740 [0.0, 2.0, 0.0, 6.0],
1741 ];
1742 let y = array![1.0, 2.0, 2.0, 4.0];
1743
1744 let groups = vec![vec![0, 1], vec![2, 3]];
1745
1746 let selector = SparseGroupLassoSelector::new()
1747 .alpha_group(0.1)
1748 .alpha_element(0.05)
1749 .feature_groups(groups);
1750
1751 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1752
1753 assert!(fitted_selector.coefficients_.is_some());
1754 assert!(fitted_selector.selected_features_.is_some());
1755 }
1756
1757 #[test]
1758 fn test_group_lasso_transform() {
1759 let x = array![
1760 [1.0, 2.0, 3.0, 4.0],
1761 [2.0, 3.0, 4.0, 5.0],
1762 [3.0, 4.0, 5.0, 6.0],
1763 ];
1764 let y = array![1.0, 2.0, 3.0];
1765
1766 let groups = vec![vec![0, 1], vec![2, 3]];
1767
1768 let selector = GroupLassoSelector::new().alpha(0.1).feature_groups(groups);
1769
1770 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1771 let transformed = fitted_selector
1772 .transform(&x)
1773 .expect("operation should succeed");
1774
1775 assert!(transformed.ncols() <= x.ncols());
1776 assert_eq!(transformed.nrows(), x.nrows());
1777 }
1778
1779 #[test]
1780 fn test_soft_threshold() {
1781 assert_eq!(soft_threshold(2.0, 1.0), 1.0);
1782 assert_eq!(soft_threshold(-2.0, 1.0), -1.0);
1783 assert_eq!(soft_threshold(0.5, 1.0), 0.0);
1784 assert_eq!(soft_threshold(-0.5, 1.0), 0.0);
1785 }
1786
1787 #[test]
1788 fn test_hierarchical_structured_sparsity_basic() {
1789 let x = array![
1790 [1.0, 0.0, 2.0, 0.0],
1791 [0.0, 1.0, 0.0, 3.0],
1792 [2.0, 0.0, 4.0, 0.0],
1793 [0.0, 2.0, 0.0, 6.0],
1794 ];
1795 let y = array![1.0, 2.0, 2.0, 4.0];
1796
1797 let mut hierarchy = HashMap::new();
1799 hierarchy.insert(0, vec![1, 2]);
1800
1801 let mut feature_to_node = HashMap::new();
1802 feature_to_node.insert(0, 0);
1803 feature_to_node.insert(1, 1);
1804 feature_to_node.insert(2, 0);
1805 feature_to_node.insert(3, 2);
1806
1807 let selector = HierarchicalStructuredSparsitySelector::new()
1808 .alpha(0.1)
1809 .hierarchy(hierarchy)
1810 .feature_to_node(feature_to_node);
1811
1812 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1813
1814 assert!(fitted_selector.coefficients_.is_some());
1815 assert!(fitted_selector.selected_features_.is_some());
1816 }
1817
1818 #[test]
1819 fn test_graph_structured_sparsity_basic() {
1820 let x = array![[1.0, 2.0, 3.0], [2.0, 3.0, 4.0], [3.0, 4.0, 5.0],];
1821 let y = array![1.0, 2.0, 3.0];
1822
1823 let adjacency_matrix = array![[0.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 0.0],];
1825
1826 let selector = GraphStructuredSparsitySelector::new()
1827 .alpha(0.1)
1828 .adjacency_matrix(adjacency_matrix);
1829
1830 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1831
1832 assert!(fitted_selector.coefficients_.is_some());
1833 assert!(fitted_selector.selected_features_.is_some());
1834 }
1835
1836 #[test]
1837 fn test_overlapping_group_sparsity_basic() {
1838 let x = array![
1839 [1.0, 0.0, 2.0, 0.0],
1840 [0.0, 1.0, 0.0, 3.0],
1841 [2.0, 0.0, 4.0, 0.0],
1842 [0.0, 2.0, 0.0, 6.0],
1843 ];
1844 let y = array![1.0, 2.0, 2.0, 4.0];
1845
1846 let overlapping_groups = vec![vec![0, 1], vec![1, 2], vec![2, 3]];
1848
1849 let selector = OverlappingGroupSparsitySelector::new()
1850 .alpha(0.1)
1851 .overlapping_groups(overlapping_groups);
1852
1853 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1854
1855 assert!(fitted_selector.coefficients_.is_some());
1856 assert!(fitted_selector.selected_features_.is_some());
1857 assert!(fitted_selector._selected_groups_.is_some());
1858 }
1859
1860 #[test]
1861 fn test_hierarchical_transform() {
1862 let x = array![
1863 [1.0, 2.0, 3.0, 4.0],
1864 [2.0, 3.0, 4.0, 5.0],
1865 [3.0, 4.0, 5.0, 6.0],
1866 ];
1867 let y = array![1.0, 2.0, 3.0];
1868
1869 let mut hierarchy = HashMap::new();
1870 hierarchy.insert(0, vec![1]);
1871
1872 let mut feature_to_node = HashMap::new();
1873 feature_to_node.insert(0, 0);
1874 feature_to_node.insert(1, 0);
1875 feature_to_node.insert(2, 1);
1876 feature_to_node.insert(3, 1);
1877
1878 let selector = HierarchicalStructuredSparsitySelector::new()
1879 .alpha(0.01) .hierarchy(hierarchy)
1881 .feature_to_node(feature_to_node);
1882
1883 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1884
1885 if !fitted_selector
1887 .selected_features_
1888 .as_ref()
1889 .expect("operation should succeed")
1890 .is_empty()
1891 {
1892 let transformed = fitted_selector
1893 .transform(&x)
1894 .expect("operation should succeed");
1895 assert!(transformed.ncols() <= x.ncols());
1896 assert_eq!(transformed.nrows(), x.nrows());
1897 } else {
1898 assert!(fitted_selector
1900 .selected_features_
1901 .as_ref()
1902 .expect("operation should succeed")
1903 .is_empty());
1904 }
1905 }
1906
1907 #[test]
1908 fn test_overlapping_group_with_group_names() {
1909 let x = array![[1.0, 2.0, 3.0], [2.0, 3.0, 4.0], [3.0, 4.0, 5.0],];
1910 let y = array![1.0, 2.0, 3.0];
1911
1912 let overlapping_groups = vec![vec![0], vec![1], vec![2]];
1913 let group_names = vec![
1914 "Group A".to_string(),
1915 "Group B".to_string(),
1916 "Group C".to_string(),
1917 ];
1918
1919 let selector = OverlappingGroupSparsitySelector::new()
1920 .alpha(0.1)
1921 .overlapping_groups(overlapping_groups)
1922 .group_names(group_names);
1923
1924 let fitted_selector = selector.fit(&x, &y).expect("operation should succeed");
1925
1926 assert!(fitted_selector.coefficients_.is_some());
1927 assert!(fitted_selector.group_names.is_some());
1928 }
1929}