sklears_feature_extraction/
statistical_features.rs1use crate::*;
7use scirs2_core::ndarray::{s, Array1, ArrayView1};
9use sklears_core::prelude::SklearsError;
10
11#[derive(Debug, Clone)]
38pub struct StatisticalMomentsExtractor {
39 max_order: usize,
40 normalize: bool,
41 include_cumulants: bool,
42 robust: bool,
43}
44
45impl StatisticalMomentsExtractor {
46 pub fn new() -> Self {
48 Self {
49 max_order: 4,
50 normalize: true,
51 include_cumulants: true,
52 robust: false,
53 }
54 }
55
56 pub fn max_order(mut self, max_order: usize) -> Self {
58 self.max_order = max_order;
59 self
60 }
61
62 pub fn normalize(mut self, normalize: bool) -> Self {
64 self.normalize = normalize;
65 self
66 }
67
68 pub fn include_cumulants(mut self, include_cumulants: bool) -> Self {
70 self.include_cumulants = include_cumulants;
71 self
72 }
73
74 pub fn robust(mut self, robust: bool) -> Self {
76 self.robust = robust;
77 self
78 }
79
80 pub fn extract_features(&self, data: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
82 let n = data.len();
83 if n == 0 {
84 return Err(SklearsError::InvalidInput(
85 "Data cannot be empty".to_string(),
86 ));
87 }
88 if n < 2 {
89 return Err(SklearsError::InvalidInput(
90 "Data must have at least 2 points".to_string(),
91 ));
92 }
93
94 let mut features = Vec::new();
95
96 let mean = if self.robust {
98 self.robust_mean(data)
99 } else {
100 data.sum() / n as Float
101 };
102 features.push(mean);
103
104 let variance = if self.robust {
105 self.robust_variance(data, mean)
106 } else {
107 data.mapv(|x| (x - mean).powi(2)).sum() / (n - 1) as Float
108 };
109 features.push(variance);
110
111 let std_dev = variance.sqrt();
112 features.push(std_dev);
113
114 for order in 1..=self.max_order {
116 let raw_moment = data.mapv(|x| x.powi(order as i32)).sum() / n as Float;
117 features.push(raw_moment);
118 }
119
120 for order in 2..=self.max_order {
122 let central_moment = data.mapv(|x| (x - mean).powi(order as i32)).sum() / n as Float;
123 features.push(central_moment);
124 }
125
126 if self.normalize && std_dev > 1e-10 {
128 for order in 3..=self.max_order {
129 let standardized_moment = data
130 .mapv(|x| ((x - mean) / std_dev).powi(order as i32))
131 .sum()
132 / n as Float;
133 features.push(standardized_moment);
134 }
135 }
136
137 if self.include_cumulants {
139 let cumulants = self.compute_cumulants(data, mean, variance)?;
140 features.extend_from_slice(&cumulants);
141 }
142
143 let skewness = if std_dev > 1e-10 {
145 data.mapv(|x| ((x - mean) / std_dev).powi(3)).sum() / n as Float
146 } else {
147 0.0
148 };
149 features.push(skewness);
150
151 let kurtosis = if std_dev > 1e-10 {
152 data.mapv(|x| ((x - mean) / std_dev).powi(4)).sum() / n as Float - 3.0
153 } else {
155 0.0
156 };
157 features.push(kurtosis);
158
159 let coefficient_of_variation = if mean.abs() > 1e-10 {
161 std_dev / mean.abs()
162 } else {
163 0.0
164 };
165 features.push(coefficient_of_variation);
166
167 Ok(Array1::from_vec(features))
168 }
169
170 fn robust_mean(&self, data: &ArrayView1<Float>) -> Float {
171 let mut sorted_data = data.to_vec();
173 sorted_data.sort_by(|a, b| a.partial_cmp(b).expect("operation should succeed"));
174 let n = sorted_data.len();
175
176 if n % 2 == 1 {
177 sorted_data[n / 2]
178 } else {
179 (sorted_data[n / 2 - 1] + sorted_data[n / 2]) / 2.0
180 }
181 }
182
183 fn robust_variance(&self, data: &ArrayView1<Float>, center: Float) -> Float {
184 let mad_values: Vec<Float> = data.iter().map(|&x| (x - center).abs()).collect();
186 let mut sorted_mad = mad_values;
187 sorted_mad.sort_by(|a, b| a.partial_cmp(b).expect("operation should succeed"));
188 let n = sorted_mad.len();
189
190 let mad = if n % 2 == 1 {
191 sorted_mad[n / 2]
192 } else {
193 (sorted_mad[n / 2 - 1] + sorted_mad[n / 2]) / 2.0
194 };
195
196 (1.4826 * mad).powi(2)
198 }
199
200 fn compute_cumulants(
201 &self,
202 data: &ArrayView1<Float>,
203 mean: Float,
204 variance: Float,
205 ) -> SklResult<Vec<Float>> {
206 let n = data.len() as Float;
207 let mut cumulants = Vec::new();
208
209 cumulants.push(mean);
211
212 cumulants.push(variance);
214
215 let third_moment = data.mapv(|x| (x - mean).powi(3)).sum() / n;
217 cumulants.push(third_moment);
218
219 let fourth_moment = data.mapv(|x| (x - mean).powi(4)).sum() / n;
221 let fourth_cumulant = fourth_moment - 3.0 * variance.powi(2);
222 cumulants.push(fourth_cumulant);
223
224 Ok(cumulants)
225 }
226}
227
228impl Default for StatisticalMomentsExtractor {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234#[derive(Debug, Clone)]
261pub struct DistributionFeaturesExtractor {
262 n_quantiles: usize,
263 n_bins: usize,
264 include_shape_features: bool,
265 density_estimation: String,
266}
267
268impl DistributionFeaturesExtractor {
269 pub fn new() -> Self {
271 Self {
272 n_quantiles: 5,
273 n_bins: 10,
274 include_shape_features: true,
275 density_estimation: "histogram".to_string(),
276 }
277 }
278
279 pub fn n_quantiles(mut self, n_quantiles: usize) -> Self {
281 self.n_quantiles = n_quantiles;
282 self
283 }
284
285 pub fn n_bins(mut self, n_bins: usize) -> Self {
287 self.n_bins = n_bins;
288 self
289 }
290
291 pub fn include_shape_features(mut self, include_shape_features: bool) -> Self {
293 self.include_shape_features = include_shape_features;
294 self
295 }
296
297 pub fn density_estimation(mut self, method: String) -> Self {
299 self.density_estimation = method;
300 self
301 }
302
303 pub fn extract_features(&self, data: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
305 let n = data.len();
306 if n == 0 {
307 return Err(SklearsError::InvalidInput(
308 "Data cannot be empty".to_string(),
309 ));
310 }
311
312 let mut features = Vec::new();
313
314 let mut sorted_data = data.to_vec();
316 sorted_data.sort_by(|a, b| a.partial_cmp(b).expect("operation should succeed"));
317
318 for i in 1..=self.n_quantiles {
320 let quantile = i as Float / (self.n_quantiles + 1) as Float;
321 let quantile_value = self.compute_quantile(&sorted_data, quantile);
322 features.push(quantile_value);
323 }
324
325 let percentiles = [0.05, 0.25, 0.5, 0.75, 0.95];
327 for &percentile in &percentiles {
328 let percentile_value = self.compute_quantile(&sorted_data, percentile);
329 features.push(percentile_value);
330 }
331
332 let q25 = self.compute_quantile(&sorted_data, 0.25);
334 let q75 = self.compute_quantile(&sorted_data, 0.75);
335 let iqr = q75 - q25;
336 features.push(iqr);
337
338 let range = sorted_data[n - 1] - sorted_data[0];
340 features.push(range);
341
342 let histogram = self.compute_histogram(&sorted_data)?;
344 features.extend_from_slice(&histogram);
345
346 if self.include_shape_features {
348 let shape_features = self.compute_shape_features(&sorted_data)?;
349 features.extend_from_slice(&shape_features);
350 }
351
352 Ok(Array1::from_vec(features))
353 }
354
355 fn compute_quantile(&self, sorted_data: &[Float], quantile: Float) -> Float {
356 let n = sorted_data.len();
357 let index = quantile * (n - 1) as Float;
358 let lower_index = index.floor() as usize;
359 let upper_index = index.ceil() as usize;
360
361 if lower_index == upper_index {
362 sorted_data[lower_index]
363 } else {
364 let weight = index - lower_index as Float;
365 sorted_data[lower_index] * (1.0 - weight) + sorted_data[upper_index] * weight
366 }
367 }
368
369 fn compute_histogram(&self, sorted_data: &[Float]) -> SklResult<Vec<Float>> {
370 if sorted_data.is_empty() {
371 return Ok(vec![0.0; self.n_bins]);
372 }
373
374 let min_val = sorted_data[0];
375 let max_val = sorted_data[sorted_data.len() - 1];
376
377 if (max_val - min_val).abs() < 1e-10 {
378 let mut hist = vec![0.0; self.n_bins];
380 hist[0] = sorted_data.len() as Float;
381 return Ok(hist);
382 }
383
384 let bin_width = (max_val - min_val) / self.n_bins as Float;
385 let mut histogram = vec![0.0; self.n_bins];
386
387 for &value in sorted_data {
388 let bin_index = ((value - min_val) / bin_width).floor() as usize;
389 let bin_index = bin_index.min(self.n_bins - 1); histogram[bin_index] += 1.0;
391 }
392
393 let total_count = sorted_data.len() as Float;
395 for count in &mut histogram {
396 *count /= total_count;
397 }
398
399 Ok(histogram)
400 }
401
402 fn compute_shape_features(&self, sorted_data: &[Float]) -> SklResult<Vec<Float>> {
403 let mut features = Vec::new();
404
405 let trim_amount = (sorted_data.len() as Float * 0.1).floor() as usize;
407 if trim_amount > 0 && trim_amount * 2 < sorted_data.len() {
408 let trimmed_slice = &sorted_data[trim_amount..sorted_data.len() - trim_amount];
409 let trimmed_mean = trimmed_slice.iter().sum::<Float>() / trimmed_slice.len() as Float;
410 features.push(trimmed_mean);
411 } else {
412 features.push(sorted_data.iter().sum::<Float>() / sorted_data.len() as Float);
413 }
414
415 let histogram = self.compute_histogram(sorted_data)?;
417 let mode_bin = histogram
418 .iter()
419 .enumerate()
420 .max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("operation should succeed"))
421 .map(|(i, _)| i)
422 .unwrap_or(0);
423
424 let min_val = sorted_data[0];
425 let max_val = sorted_data[sorted_data.len() - 1];
426 let bin_width = (max_val - min_val) / self.n_bins as Float;
427 let mode_estimate = min_val + (mode_bin as Float + 0.5) * bin_width;
428 features.push(mode_estimate);
429
430 let mut peak_count = 0;
432 for i in 1..histogram.len() - 1 {
433 if histogram[i] > histogram[i - 1] && histogram[i] > histogram[i + 1] {
434 peak_count += 1;
435 }
436 }
437 features.push(peak_count as Float);
438
439 let tail_size = (self.n_bins as Float * 0.1).max(1.0) as usize;
441 let left_tail: Float = histogram[0..tail_size].iter().sum();
442 let right_tail: Float = histogram[histogram.len() - tail_size..].iter().sum();
443 features.push(left_tail + right_tail);
444
445 Ok(features)
446 }
447}
448
449impl Default for DistributionFeaturesExtractor {
450 fn default() -> Self {
451 Self::new()
452 }
453}
454
455#[derive(Debug, Clone)]
482pub struct EntropyFeaturesExtractor {
483 n_bins: usize,
484 include_conditional_entropy: bool,
485 window_size: usize,
486 tolerance: Float,
487}
488
489impl EntropyFeaturesExtractor {
490 pub fn new() -> Self {
492 Self {
493 n_bins: 10,
494 include_conditional_entropy: true,
495 window_size: 2,
496 tolerance: 0.2,
497 }
498 }
499
500 pub fn n_bins(mut self, n_bins: usize) -> Self {
502 self.n_bins = n_bins;
503 self
504 }
505
506 pub fn include_conditional_entropy(mut self, include_conditional_entropy: bool) -> Self {
508 self.include_conditional_entropy = include_conditional_entropy;
509 self
510 }
511
512 pub fn window_size(mut self, window_size: usize) -> Self {
514 self.window_size = window_size;
515 self
516 }
517
518 pub fn tolerance(mut self, tolerance: Float) -> Self {
520 self.tolerance = tolerance;
521 self
522 }
523
524 pub fn extract_features(&self, data: &ArrayView1<Float>) -> SklResult<Array1<Float>> {
526 let n = data.len();
527 if n == 0 {
528 return Err(SklearsError::InvalidInput(
529 "Data cannot be empty".to_string(),
530 ));
531 }
532
533 let mut features = Vec::new();
534
535 let shannon_entropy = self.compute_shannon_entropy(data)?;
537 features.push(shannon_entropy);
538
539 let renyi_entropy = self.compute_renyi_entropy(data, 2.0)?;
541 features.push(renyi_entropy);
542
543 if n >= 2 * self.window_size {
545 let sample_entropy = self.compute_sample_entropy(data)?;
546 features.push(sample_entropy);
547 } else {
548 features.push(0.0); }
550
551 if n > self.window_size {
553 let approximate_entropy = self.compute_approximate_entropy(data)?;
554 features.push(approximate_entropy);
555 } else {
556 features.push(0.0); }
558
559 let differential_entropy = self.compute_differential_entropy(data)?;
561 features.push(differential_entropy);
562
563 if self.include_conditional_entropy && n >= 2 {
565 let conditional_entropy = self.compute_conditional_entropy(data)?;
566 features.push(conditional_entropy);
567 }
568
569 Ok(Array1::from_vec(features))
570 }
571
572 fn compute_shannon_entropy(&self, data: &ArrayView1<Float>) -> SklResult<Float> {
573 let histogram = self.compute_normalized_histogram(data)?;
574
575 let mut entropy = 0.0;
576 for &prob in &histogram {
577 if prob > 1e-10 {
578 entropy -= prob * prob.ln();
579 }
580 }
581
582 Ok(entropy)
583 }
584
585 fn compute_renyi_entropy(&self, data: &ArrayView1<Float>, alpha: Float) -> SklResult<Float> {
586 if (alpha - 1.0).abs() < 1e-10 {
587 return self.compute_shannon_entropy(data); }
589
590 let histogram = self.compute_normalized_histogram(data)?;
591
592 let mut sum = 0.0;
593 for &prob in &histogram {
594 if prob > 1e-10 {
595 sum += prob.powf(alpha);
596 }
597 }
598
599 let entropy = if sum > 1e-10 {
600 sum.ln() / (1.0 - alpha)
601 } else {
602 0.0
603 };
604
605 Ok(entropy)
606 }
607
608 fn compute_sample_entropy(&self, data: &ArrayView1<Float>) -> SklResult<Float> {
609 let n = data.len();
610 let m = self.window_size;
611 let r = self.tolerance;
612
613 if n < m + 1 {
614 return Ok(0.0);
615 }
616
617 let mut a = 0;
618 let mut b = 0;
619
620 for i in 0..(n - m) {
621 for j in 0..(n - m) {
622 if i != j {
623 let mut match_m = true;
624 let mut _match_m1 = true;
625
626 for k in 0..m {
628 if (data[i + k] - data[j + k]).abs() > r {
629 match_m = false;
630 _match_m1 = false;
631 break;
632 }
633 }
634
635 if match_m {
636 b += 1;
637
638 if i < n - m && j < n - m {
640 if (data[i + m] - data[j + m]).abs() <= r {
641 a += 1;
642 } else {
643 _match_m1 = false;
644 }
645 } else {
646 _match_m1 = false;
647 }
648 }
649 }
650 }
651 }
652
653 let sample_entropy = if a > 0 && b > 0 {
654 -(a as Float / b as Float).ln()
655 } else {
656 std::f64::consts::E };
658
659 Ok(sample_entropy)
660 }
661
662 fn compute_approximate_entropy(&self, data: &ArrayView1<Float>) -> SklResult<Float> {
663 let n = data.len();
664 let m = self.window_size;
665 let r = self.tolerance;
666
667 if n < m + 1 {
668 return Ok(0.0);
669 }
670
671 let phi_m = self.compute_phi(data, m, r)?;
672 let phi_m1 = self.compute_phi(data, m + 1, r)?;
673
674 Ok(phi_m - phi_m1)
675 }
676
677 fn compute_phi(&self, data: &ArrayView1<Float>, m: usize, r: Float) -> SklResult<Float> {
678 let n = data.len();
679 let mut phi = 0.0;
680
681 for i in 0..(n - m + 1) {
682 let mut pattern_count = 0;
683
684 for j in 0..(n - m + 1) {
685 let mut matches = true;
686 for k in 0..m {
687 if (data[i + k] - data[j + k]).abs() > r {
688 matches = false;
689 break;
690 }
691 }
692 if matches {
693 pattern_count += 1;
694 }
695 }
696
697 if pattern_count > 0 {
698 phi += (pattern_count as Float / (n - m + 1) as Float).ln();
699 }
700 }
701
702 phi /= (n - m + 1) as Float;
703 Ok(phi)
704 }
705
706 fn compute_differential_entropy(&self, data: &ArrayView1<Float>) -> SklResult<Float> {
707 let mean = data.sum() / data.len() as Float;
709 let variance = data.mapv(|x| (x - mean).powi(2)).sum() / (data.len() - 1) as Float;
710
711 let differential_entropy =
712 0.5 * (2.0 * std::f64::consts::PI * std::f64::consts::E * variance).ln();
713 Ok(differential_entropy)
714 }
715
716 fn compute_conditional_entropy(&self, data: &ArrayView1<Float>) -> SklResult<Float> {
717 let n = data.len();
719 if n < 2 {
720 return Ok(0.0);
721 }
722
723 let pairs: Vec<(Float, Float)> = (0..n - 1).map(|i| (data[i], data[i + 1])).collect();
725
726 let joint_entropy = self.compute_joint_entropy(&pairs)?;
728 let marginal_entropy = self.compute_shannon_entropy(&data.slice(s![0..n - 1]))?;
729
730 Ok(joint_entropy - marginal_entropy)
731 }
732
733 fn compute_joint_entropy(&self, pairs: &[(Float, Float)]) -> SklResult<Float> {
734 let n_bins = self.n_bins;
736
737 if pairs.is_empty() {
738 return Ok(0.0);
739 }
740
741 let x_min = pairs
743 .iter()
744 .map(|(x, _)| *x)
745 .fold(f64::INFINITY, |a, b| a.min(b));
746 let x_max = pairs
747 .iter()
748 .map(|(x, _)| *x)
749 .fold(f64::NEG_INFINITY, |a, b| a.max(b));
750 let y_min = pairs
751 .iter()
752 .map(|(_, y)| *y)
753 .fold(f64::INFINITY, |a, b| a.min(b));
754 let y_max = pairs
755 .iter()
756 .map(|(_, y)| *y)
757 .fold(f64::NEG_INFINITY, |a, b| a.max(b));
758
759 let x_range = if (x_max - x_min).abs() < 1e-10 {
760 1.0
761 } else {
762 x_max - x_min
763 };
764 let y_range = if (y_max - y_min).abs() < 1e-10 {
765 1.0
766 } else {
767 y_max - y_min
768 };
769
770 let mut joint_hist = vec![vec![0.0; n_bins]; n_bins];
771
772 for &(x, y) in pairs {
773 let x_bin = ((x - x_min) / x_range * n_bins as Float).floor() as usize;
774 let y_bin = ((y - y_min) / y_range * n_bins as Float).floor() as usize;
775 let x_bin = x_bin.min(n_bins - 1);
776 let y_bin = y_bin.min(n_bins - 1);
777 joint_hist[x_bin][y_bin] += 1.0;
778 }
779
780 let total = pairs.len() as Float;
782 let mut entropy = 0.0;
783
784 for row in joint_hist.iter().take(n_bins) {
785 for &count in row.iter().take(n_bins) {
786 let prob = count / total;
787 if prob > 1e-10 {
788 entropy -= prob * prob.ln();
789 }
790 }
791 }
792
793 Ok(entropy)
794 }
795
796 fn compute_normalized_histogram(&self, data: &ArrayView1<Float>) -> SklResult<Vec<Float>> {
797 let n = data.len();
798 if n == 0 {
799 return Ok(vec![0.0; self.n_bins]);
800 }
801
802 let min_val = data.iter().cloned().fold(f64::INFINITY, |a, b| a.min(b));
803 let max_val = data
804 .iter()
805 .cloned()
806 .fold(f64::NEG_INFINITY, |a, b| a.max(b));
807
808 if (max_val - min_val).abs() < 1e-10 {
809 let mut hist = vec![0.0; self.n_bins];
811 hist[0] = 1.0;
812 return Ok(hist);
813 }
814
815 let bin_width = (max_val - min_val) / self.n_bins as Float;
816 let mut histogram = vec![0.0; self.n_bins];
817
818 for &value in data.iter() {
819 let bin_index = ((value - min_val) / bin_width).floor() as usize;
820 let bin_index = bin_index.min(self.n_bins - 1);
821 histogram[bin_index] += 1.0;
822 }
823
824 let total_count = n as Float;
826 for count in &mut histogram {
827 *count /= total_count;
828 }
829
830 Ok(histogram)
831 }
832}
833
834impl Default for EntropyFeaturesExtractor {
835 fn default() -> Self {
836 Self::new()
837 }
838}