fdars_core/classification/
mod.rs1use crate::error::FdarError;
14use crate::matrix::FdMatrix;
15use crate::regression::fdata_to_pc_1d;
16
17pub mod cv;
18pub mod dd;
19pub mod fit;
20pub mod kernel;
21pub mod knn;
22pub mod lda;
23pub mod qda;
24
25#[cfg(test)]
26mod tests;
27
28#[derive(Debug, Clone, PartialEq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[non_exhaustive]
36pub struct ClassifResult {
37 pub predicted: Vec<usize>,
39 pub probabilities: Option<FdMatrix>,
41 pub accuracy: f64,
43 pub confusion: Vec<Vec<usize>>,
45 pub n_classes: usize,
47 pub ncomp: usize,
49}
50
51#[derive(Debug, Clone, PartialEq)]
53#[non_exhaustive]
54pub struct ClassifCvResult {
55 pub error_rate: f64,
57 pub fold_errors: Vec<f64>,
59 pub best_ncomp: usize,
61}
62
63pub(crate) fn remap_labels(y: &[usize]) -> (Vec<usize>, usize) {
69 let mut labels: Vec<usize> = y.to_vec();
70 let mut unique: Vec<usize> = y.to_vec();
71 unique.sort_unstable();
72 unique.dedup();
73 let g = unique.len();
74 for label in &mut labels {
75 *label = unique.iter().position(|&u| u == *label).unwrap_or(0);
76 }
77 (labels, g)
78}
79
80fn confusion_matrix(true_labels: &[usize], pred_labels: &[usize], g: usize) -> Vec<Vec<usize>> {
82 let mut cm = vec![vec![0usize; g]; g];
83 for (&t, &p) in true_labels.iter().zip(pred_labels.iter()) {
84 if t < g && p < g {
85 cm[t][p] += 1;
86 }
87 }
88 cm
89}
90
91pub(crate) fn class_means_and_priors(
93 features: &FdMatrix,
94 labels: &[usize],
95 g: usize,
96) -> (Vec<Vec<f64>>, Vec<usize>, Vec<f64>) {
97 let n = features.nrows();
98 let d = features.ncols();
99 let mut counts = vec![0usize; g];
100 let mut class_means = vec![vec![0.0; d]; g];
101 for i in 0..n {
102 let c = labels[i];
103 counts[c] += 1;
104 for j in 0..d {
105 class_means[c][j] += features[(i, j)];
106 }
107 }
108 for c in 0..g {
109 if counts[c] > 0 {
110 for j in 0..d {
111 class_means[c][j] /= counts[c] as f64;
112 }
113 }
114 }
115 let priors: Vec<f64> = counts.iter().map(|&c| c as f64 / n as f64).collect();
116 (class_means, counts, priors)
117}
118
119fn compute_accuracy(true_labels: &[usize], pred_labels: &[usize]) -> f64 {
121 let n = true_labels.len();
122 if n == 0 {
123 return 0.0;
124 }
125 let correct = true_labels
126 .iter()
127 .zip(pred_labels.iter())
128 .filter(|(&t, &p)| t == p)
129 .count();
130 correct as f64 / n as f64
131}
132
133pub(crate) fn build_feature_matrix(
135 data: &FdMatrix,
136 scalar_covariates: Option<&FdMatrix>,
137 ncomp: usize,
138) -> Result<(FdMatrix, Vec<f64>, FdMatrix, Vec<f64>), FdarError> {
139 let m = data.ncols();
140 let argvals: Vec<f64> = (0..m).map(|j| j as f64 / (m - 1).max(1) as f64).collect();
141 let fpca = fdata_to_pc_1d(data, ncomp, &argvals)?;
142 let n = data.nrows();
143 let d_pc = fpca.scores.ncols();
144 let d_cov = scalar_covariates.map_or(0, super::matrix::FdMatrix::ncols);
145 let d = d_pc + d_cov;
146
147 let mut features = FdMatrix::zeros(n, d);
148 for i in 0..n {
149 for j in 0..d_pc {
150 features[(i, j)] = fpca.scores[(i, j)];
151 }
152 if let Some(cov) = scalar_covariates {
153 for j in 0..d_cov {
154 features[(i, d_pc + j)] = cov[(i, j)];
155 }
156 }
157 }
158
159 Ok((features, fpca.mean, fpca.rotation, fpca.weights))
160}
161
162pub use cv::fclassif_cv;
167pub use dd::fclassif_dd;
168pub(crate) use fit::classif_predict_probs;
169pub use fit::{
170 fclassif_cv_with_config, fclassif_knn_fit, fclassif_lda_fit, fclassif_qda_fit, ClassifCvConfig,
171 ClassifFit, ClassifMethod,
172};
173pub use kernel::{fclassif_kernel, kernel_classify_from_distances};
174pub use knn::{fclassif_knn, knn_classify_from_distances};
175pub use lda::fclassif_lda;
176pub use qda::fclassif_qda;