1use crate::matrix::FdMatrix;
39use std::collections::HashMap;
40
41#[derive(Debug, Clone)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct FdaData {
50 pub curves: Option<FdMatrix>,
53 pub argvals: Option<Vec<f64>>,
55
56 pub grouping: Vec<GroupVar>,
59 pub scalar_vars: Vec<NamedVec>,
61 pub tabular: Option<FdMatrix>,
63 pub column_names: Option<Vec<String>>,
65
66 pub layers: HashMap<LayerKey, Layer>,
69}
70
71#[derive(Debug, Clone)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74pub struct NamedVec {
75 pub name: String,
76 pub values: Vec<f64>,
77}
78
79#[derive(Debug, Clone)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct GroupVar {
83 pub name: String,
85 pub labels: Vec<String>,
87 pub unique: Vec<String>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Hash)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96#[non_exhaustive]
97pub enum LayerKey {
98 Fpca,
100 Pls,
102 Alignment,
104 Distances,
106 Depth,
108 Outliers,
110 Clusters,
112 Regression,
114 FunctionOnScalar,
116 Tolerance,
118 Mean,
120 SpmChart,
122 SpmMonitor,
124 Explain,
126 Custom(String),
128}
129
130#[derive(Debug, Clone)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133#[non_exhaustive]
134pub enum Layer {
135 Fpca(FpcaLayer),
136 Pls(PlsLayer),
137 Alignment(AlignmentLayer),
138 Distances(DistancesLayer),
139 Depth(DepthLayer),
140 Outliers(OutlierLayer),
141 Clusters(ClusterLayer),
142 Regression(RegressionLayer),
143 FunctionOnScalar(FosrLayer),
144 Tolerance(ToleranceLayer),
145 Mean(MeanLayer),
146 SpmChart(SpmChartLayer),
147 SpmMonitor(SpmMonitorLayer),
148 Explain(ExplainLayer),
149 Custom(CustomLayer),
150}
151
152#[derive(Debug, Clone)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
157pub struct FpcaLayer {
158 pub eigenvalues: Vec<f64>,
159 pub variance_explained: Vec<f64>,
160 pub eigenfunctions: FdMatrix,
162 pub scores: FdMatrix,
164 pub mean: Vec<f64>,
166 pub weights: Vec<f64>,
168 pub ncomp: usize,
169}
170
171#[derive(Debug, Clone)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174pub struct PlsLayer {
175 pub weights: FdMatrix,
177 pub scores: FdMatrix,
179 pub loadings: FdMatrix,
181 pub ncomp: usize,
182}
183
184#[derive(Debug, Clone)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187pub struct AlignmentLayer {
188 pub aligned: FdMatrix,
190 pub warps: FdMatrix,
192 pub mean: Vec<f64>,
194 pub mean_srsf: Vec<f64>,
196 pub n_iter: Option<usize>,
198 pub converged: Option<bool>,
200}
201
202#[derive(Debug, Clone)]
204#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
205pub struct DistancesLayer {
206 pub dist_mat: FdMatrix,
208 pub method: String,
210}
211
212#[derive(Debug, Clone)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215pub struct DepthLayer {
216 pub scores: Vec<f64>,
218 pub method: String,
220}
221
222#[derive(Debug, Clone)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225pub struct OutlierLayer {
226 pub flags: Vec<bool>,
228 pub threshold: f64,
230 pub method: String,
232 pub mei: Option<Vec<f64>>,
234 pub mbd: Option<Vec<f64>>,
236 pub magnitude: Option<Vec<f64>>,
238 pub shape: Option<Vec<f64>>,
240 pub outliergram_a0: Option<f64>,
242 pub outliergram_a1: Option<f64>,
244 pub outliergram_a2: Option<f64>,
246}
247
248#[derive(Debug, Clone)]
250#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
251pub struct ClusterLayer {
252 pub labels: Vec<usize>,
254 pub k: usize,
256 pub method: String,
258 pub centers: Option<FdMatrix>,
260 pub medoid_indices: Option<Vec<usize>>,
262 pub silhouette: Option<Vec<f64>>,
264}
265
266#[derive(Debug, Clone)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
269pub struct RegressionLayer {
270 pub method: String,
272 pub beta_t: Option<Vec<f64>>,
274 pub fitted_values: Vec<f64>,
276 pub residuals: Vec<f64>,
278 pub observed_y: Vec<f64>,
280 pub r_squared: f64,
282 pub adj_r_squared: Option<f64>,
284 pub intercept: f64,
286 pub ncomp: usize,
288 pub argvals: Option<Vec<f64>>,
290 pub beta_se: Option<Vec<f64>>,
292 pub model_name: Option<String>,
294 pub n_obs: Option<usize>,
296}
297
298#[derive(Debug, Clone)]
300#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
301pub struct FosrLayer {
302 pub coefficients: FdMatrix,
304 pub fitted: FdMatrix,
306 pub r_squared_t: Vec<f64>,
308}
309
310#[derive(Debug, Clone)]
312#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
313pub struct ToleranceLayer {
314 pub lower: Vec<f64>,
316 pub upper: Vec<f64>,
318 pub center: Vec<f64>,
320 pub method: String,
322}
323
324#[derive(Debug, Clone)]
326#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
327pub struct MeanLayer {
328 pub mean: Vec<f64>,
330}
331
332#[derive(Debug, Clone)]
334#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
335pub struct SpmChartLayer {
336 pub t2_limit: f64,
338 pub spe_limit: f64,
340 pub t2_stats: Vec<f64>,
342 pub spe_stats: Vec<f64>,
344 pub ncomp: usize,
346 pub alpha: f64,
348 pub eigenvalues: Option<Vec<f64>>,
350 pub fpca_mean: Option<Vec<f64>>,
352 pub fpca_weights: Option<Vec<f64>>,
354}
355
356#[derive(Debug, Clone)]
358#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
359pub struct SpmMonitorLayer {
360 pub t2_stats: Vec<f64>,
362 pub spe_stats: Vec<f64>,
364 pub t2_limit: f64,
366 pub spe_limit: f64,
368 pub t2_alarms: Vec<bool>,
370 pub spe_alarms: Vec<bool>,
372}
373
374#[derive(Debug, Clone)]
376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
377pub struct ExplainLayer {
378 pub method: String,
380 pub values: Vec<f64>,
382 pub labels: Vec<String>,
384 pub extra: Option<HashMap<String, Vec<f64>>>,
386}
387
388#[derive(Debug, Clone)]
390#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
391pub struct CustomLayer {
392 pub name: String,
393 pub data: HashMap<String, Vec<f64>>,
394}
395
396impl FdaData {
399 pub fn from_curves(curves: FdMatrix, argvals: Vec<f64>) -> Self {
401 Self {
402 curves: Some(curves),
403 argvals: Some(argvals),
404 grouping: Vec::new(),
405 scalar_vars: Vec::new(),
406 tabular: None,
407 column_names: None,
408 layers: HashMap::new(),
409 }
410 }
411
412 pub fn from_tabular(tabular: FdMatrix, column_names: Vec<String>) -> Self {
414 Self {
415 curves: None,
416 argvals: None,
417 grouping: Vec::new(),
418 scalar_vars: Vec::new(),
419 tabular: Some(tabular),
420 column_names: Some(column_names),
421 layers: HashMap::new(),
422 }
423 }
424
425 pub fn empty() -> Self {
427 Self {
428 curves: None,
429 argvals: None,
430 grouping: Vec::new(),
431 scalar_vars: Vec::new(),
432 tabular: None,
433 column_names: None,
434 layers: HashMap::new(),
435 }
436 }
437
438 pub fn require_curves(&self) -> Result<(&FdMatrix, &[f64]), String> {
442 match (&self.curves, &self.argvals) {
443 (Some(c), Some(a)) => Ok((c, a)),
444 _ => Err("FdaData requires functional curves + argvals".into()),
445 }
446 }
447
448 pub fn require_layer(&self, key: &LayerKey) -> Result<&Layer, String> {
450 self.layers
451 .get(key)
452 .ok_or_else(|| format!("FdaData missing required layer: {key:?}"))
453 }
454
455 pub fn has_layer(&self, key: &LayerKey) -> bool {
459 self.layers.contains_key(key)
460 }
461
462 pub fn get_layer(&self, key: &LayerKey) -> Option<&Layer> {
464 self.layers.get(key)
465 }
466
467 pub fn set_layer(&mut self, key: LayerKey, layer: Layer) {
469 self.layers.insert(key, layer);
470 }
471
472 pub fn remove_layer(&mut self, key: &LayerKey) -> Option<Layer> {
474 self.layers.remove(key)
475 }
476
477 pub fn layer_keys(&self) -> Vec<&LayerKey> {
479 self.layers.keys().collect()
480 }
481
482 pub fn fpca(&self) -> Option<&FpcaLayer> {
486 match self.layers.get(&LayerKey::Fpca)? {
487 Layer::Fpca(l) => Some(l),
488 _ => None,
489 }
490 }
491
492 pub fn distances(&self) -> Option<&DistancesLayer> {
494 match self.layers.get(&LayerKey::Distances)? {
495 Layer::Distances(l) => Some(l),
496 _ => None,
497 }
498 }
499
500 pub fn alignment(&self) -> Option<&AlignmentLayer> {
502 match self.layers.get(&LayerKey::Alignment)? {
503 Layer::Alignment(l) => Some(l),
504 _ => None,
505 }
506 }
507
508 pub fn regression(&self) -> Option<&RegressionLayer> {
510 match self.layers.get(&LayerKey::Regression)? {
511 Layer::Regression(l) => Some(l),
512 _ => None,
513 }
514 }
515
516 pub fn clusters(&self) -> Option<&ClusterLayer> {
518 match self.layers.get(&LayerKey::Clusters)? {
519 Layer::Clusters(l) => Some(l),
520 _ => None,
521 }
522 }
523
524 pub fn depth(&self) -> Option<&DepthLayer> {
526 match self.layers.get(&LayerKey::Depth)? {
527 Layer::Depth(l) => Some(l),
528 _ => None,
529 }
530 }
531
532 pub fn outliers(&self) -> Option<&OutlierLayer> {
534 match self.layers.get(&LayerKey::Outliers)? {
535 Layer::Outliers(l) => Some(l),
536 _ => None,
537 }
538 }
539
540 pub fn n_obs(&self) -> usize {
544 if let Some(c) = &self.curves {
545 return c.nrows();
546 }
547 if let Some(t) = &self.tabular {
548 return t.nrows();
549 }
550 self.scalar_vars.first().map_or(0, |v| v.values.len())
551 }
552
553 pub fn n_points(&self) -> usize {
555 self.argvals.as_ref().map_or(0, |a| a.len())
556 }
557
558 pub fn add_scalar(&mut self, name: impl Into<String>, values: Vec<f64>) {
560 self.scalar_vars.push(NamedVec {
561 name: name.into(),
562 values,
563 });
564 }
565
566 pub fn get_scalar(&self, name: &str) -> Option<&[f64]> {
568 self.scalar_vars
569 .iter()
570 .find(|v| v.name == name)
571 .map(|v| v.values.as_slice())
572 }
573
574 pub fn add_grouping(&mut self, name: impl Into<String>, labels: Vec<String>) {
578 let mut unique = Vec::new();
579 for lab in &labels {
580 if !unique.contains(lab) {
581 unique.push(lab.clone());
582 }
583 }
584 self.grouping.push(GroupVar {
585 name: name.into(),
586 labels,
587 unique,
588 });
589 }
590
591 pub fn get_grouping(&self, name: &str) -> Option<&GroupVar> {
593 self.grouping.iter().find(|g| g.name == name)
594 }
595}
596
597#[cfg(test)]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn from_curves_basic() {
605 let fd = FdaData::from_curves(
606 FdMatrix::zeros(10, 50),
607 (0..50).map(|i| i as f64 / 49.0).collect(),
608 );
609 assert_eq!(fd.n_obs(), 10);
610 assert_eq!(fd.n_points(), 50);
611 assert!(fd.require_curves().is_ok());
612 assert!(!fd.has_layer(&LayerKey::Fpca));
613 }
614
615 #[test]
616 fn add_and_retrieve_layers() {
617 let mut fd = FdaData::from_curves(
618 FdMatrix::zeros(5, 20),
619 (0..20).map(|i| i as f64 / 19.0).collect(),
620 );
621
622 fd.set_layer(
623 LayerKey::Depth,
624 Layer::Depth(DepthLayer {
625 scores: vec![0.5; 5],
626 method: "fraiman_muniz".into(),
627 }),
628 );
629
630 assert!(fd.has_layer(&LayerKey::Depth));
631 assert!(!fd.has_layer(&LayerKey::Fpca));
632 assert!(fd.depth().is_some());
633 assert_eq!(fd.depth().unwrap().scores.len(), 5);
634 assert_eq!(fd.layer_keys().len(), 1);
635 }
636
637 #[test]
638 fn require_missing_layer_errors() {
639 let fd = FdaData::from_curves(FdMatrix::zeros(3, 10), vec![0.0; 10]);
640 assert!(fd.require_layer(&LayerKey::Fpca).is_err());
641 }
642
643 #[test]
644 fn scalar_vars() {
645 let mut fd = FdaData::empty();
646 fd.add_scalar("height", vec![170.0, 180.0, 165.0]);
647 assert_eq!(fd.get_scalar("height").unwrap(), &[170.0, 180.0, 165.0]);
648 assert!(fd.get_scalar("weight").is_none());
649 assert_eq!(fd.n_obs(), 3);
650 }
651
652 #[test]
653 fn multiple_layers_compose() {
654 let mut fd = FdaData::from_curves(FdMatrix::zeros(10, 30), vec![0.0; 30]);
655
656 fd.set_layer(
657 LayerKey::Depth,
658 Layer::Depth(DepthLayer {
659 scores: vec![0.5; 10],
660 method: "fm".into(),
661 }),
662 );
663 fd.set_layer(
664 LayerKey::Outliers,
665 Layer::Outliers(OutlierLayer {
666 flags: vec![false; 10],
667 threshold: 0.1,
668 method: "lrt".into(),
669 mei: None,
670 mbd: None,
671 magnitude: None,
672 shape: None,
673 outliergram_a0: None,
674 outliergram_a1: None,
675 outliergram_a2: None,
676 }),
677 );
678 fd.set_layer(
679 LayerKey::Distances,
680 Layer::Distances(DistancesLayer {
681 dist_mat: FdMatrix::zeros(10, 10),
682 method: "elastic".into(),
683 }),
684 );
685
686 assert_eq!(fd.layer_keys().len(), 3);
687 assert!(fd.depth().is_some());
688 assert!(fd.outliers().is_some());
689 assert!(fd.distances().is_some());
690 }
691}