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: Option<Vec<usize>>,
59 pub group_names: Option<Vec<String>>,
61 pub scalar_vars: Vec<NamedVec>,
63 pub tabular: Option<FdMatrix>,
65 pub column_names: Option<Vec<String>>,
67
68 pub layers: HashMap<LayerKey, Layer>,
71}
72
73#[derive(Debug, Clone)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct NamedVec {
77 pub name: String,
78 pub values: Vec<f64>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Hash)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86#[non_exhaustive]
87pub enum LayerKey {
88 Fpca,
90 Pls,
92 Alignment,
94 Distances,
96 Depth,
98 Outliers,
100 Clusters,
102 Regression,
104 FunctionOnScalar,
106 Tolerance,
108 Mean,
110 SpmChart,
112 SpmMonitor,
114 Explain,
116 Custom(String),
118}
119
120#[derive(Debug, Clone)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[non_exhaustive]
124pub enum Layer {
125 Fpca(FpcaLayer),
126 Pls(PlsLayer),
127 Alignment(AlignmentLayer),
128 Distances(DistancesLayer),
129 Depth(DepthLayer),
130 Outliers(OutlierLayer),
131 Clusters(ClusterLayer),
132 Regression(RegressionLayer),
133 FunctionOnScalar(FosrLayer),
134 Tolerance(ToleranceLayer),
135 Mean(MeanLayer),
136 SpmChart(SpmChartLayer),
137 SpmMonitor(SpmMonitorLayer),
138 Explain(ExplainLayer),
139 Custom(CustomLayer),
140}
141
142#[derive(Debug, Clone)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub struct FpcaLayer {
148 pub eigenvalues: Vec<f64>,
149 pub variance_explained: Vec<f64>,
150 pub eigenfunctions: FdMatrix,
152 pub scores: FdMatrix,
154 pub mean: Vec<f64>,
156 pub weights: Vec<f64>,
158 pub ncomp: usize,
159}
160
161#[derive(Debug, Clone)]
163#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
164pub struct PlsLayer {
165 pub weights: FdMatrix,
167 pub scores: FdMatrix,
169 pub loadings: FdMatrix,
171 pub ncomp: usize,
172}
173
174#[derive(Debug, Clone)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177pub struct AlignmentLayer {
178 pub aligned: FdMatrix,
180 pub warps: FdMatrix,
182 pub mean: Vec<f64>,
184 pub mean_srsf: Vec<f64>,
186}
187
188#[derive(Debug, Clone)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191pub struct DistancesLayer {
192 pub dist_mat: FdMatrix,
194 pub method: String,
196}
197
198#[derive(Debug, Clone)]
200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201pub struct DepthLayer {
202 pub scores: Vec<f64>,
204 pub method: String,
206}
207
208#[derive(Debug, Clone)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211pub struct OutlierLayer {
212 pub flags: Vec<bool>,
214 pub threshold: f64,
216 pub method: String,
218 pub mei: Option<Vec<f64>>,
220 pub mbd: Option<Vec<f64>>,
222 pub magnitude: Option<Vec<f64>>,
224 pub shape: Option<Vec<f64>>,
226}
227
228#[derive(Debug, Clone)]
230#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
231pub struct ClusterLayer {
232 pub labels: Vec<usize>,
234 pub k: usize,
236 pub method: String,
238 pub centers: Option<FdMatrix>,
240 pub medoid_indices: Option<Vec<usize>>,
242 pub silhouette: Option<Vec<f64>>,
244}
245
246#[derive(Debug, Clone)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
249pub struct RegressionLayer {
250 pub method: String,
252 pub beta_t: Option<Vec<f64>>,
254 pub fitted_values: Vec<f64>,
256 pub residuals: Vec<f64>,
258 pub observed_y: Vec<f64>,
260 pub r_squared: f64,
262 pub adj_r_squared: Option<f64>,
264 pub intercept: f64,
266 pub ncomp: usize,
268 pub argvals: Option<Vec<f64>>,
270 pub beta_se: Option<Vec<f64>>,
272}
273
274#[derive(Debug, Clone)]
276#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
277pub struct FosrLayer {
278 pub coefficients: FdMatrix,
280 pub fitted: FdMatrix,
282 pub r_squared_t: Vec<f64>,
284}
285
286#[derive(Debug, Clone)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
289pub struct ToleranceLayer {
290 pub lower: Vec<f64>,
292 pub upper: Vec<f64>,
294 pub center: Vec<f64>,
296 pub method: String,
298}
299
300#[derive(Debug, Clone)]
302#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
303pub struct MeanLayer {
304 pub mean: Vec<f64>,
306}
307
308#[derive(Debug, Clone)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311pub struct SpmChartLayer {
312 pub t2_limit: f64,
314 pub spe_limit: f64,
316 pub t2_stats: Vec<f64>,
318 pub spe_stats: Vec<f64>,
320 pub ncomp: usize,
322 pub alpha: f64,
324}
325
326#[derive(Debug, Clone)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329pub struct SpmMonitorLayer {
330 pub t2_stats: Vec<f64>,
332 pub spe_stats: Vec<f64>,
334 pub t2_limit: f64,
336 pub spe_limit: f64,
338 pub t2_alarms: Vec<bool>,
340 pub spe_alarms: Vec<bool>,
342}
343
344#[derive(Debug, Clone)]
346#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
347pub struct ExplainLayer {
348 pub method: String,
350 pub values: Vec<f64>,
352 pub labels: Vec<String>,
354 pub extra: Option<HashMap<String, Vec<f64>>>,
356}
357
358#[derive(Debug, Clone)]
360#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
361pub struct CustomLayer {
362 pub name: String,
363 pub data: HashMap<String, Vec<f64>>,
364}
365
366impl FdaData {
369 pub fn from_curves(curves: FdMatrix, argvals: Vec<f64>) -> Self {
371 Self {
372 curves: Some(curves),
373 argvals: Some(argvals),
374 grouping: None,
375 group_names: None,
376 scalar_vars: Vec::new(),
377 tabular: None,
378 column_names: None,
379 layers: HashMap::new(),
380 }
381 }
382
383 pub fn from_tabular(tabular: FdMatrix, column_names: Vec<String>) -> Self {
385 Self {
386 curves: None,
387 argvals: None,
388 grouping: None,
389 group_names: None,
390 scalar_vars: Vec::new(),
391 tabular: Some(tabular),
392 column_names: Some(column_names),
393 layers: HashMap::new(),
394 }
395 }
396
397 pub fn empty() -> Self {
399 Self {
400 curves: None,
401 argvals: None,
402 grouping: None,
403 group_names: None,
404 scalar_vars: Vec::new(),
405 tabular: None,
406 column_names: None,
407 layers: HashMap::new(),
408 }
409 }
410
411 pub fn require_curves(&self) -> Result<(&FdMatrix, &[f64]), String> {
415 match (&self.curves, &self.argvals) {
416 (Some(c), Some(a)) => Ok((c, a)),
417 _ => Err("FdaData requires functional curves + argvals".into()),
418 }
419 }
420
421 pub fn require_layer(&self, key: &LayerKey) -> Result<&Layer, String> {
423 self.layers
424 .get(key)
425 .ok_or_else(|| format!("FdaData missing required layer: {key:?}"))
426 }
427
428 pub fn has_layer(&self, key: &LayerKey) -> bool {
432 self.layers.contains_key(key)
433 }
434
435 pub fn get_layer(&self, key: &LayerKey) -> Option<&Layer> {
437 self.layers.get(key)
438 }
439
440 pub fn set_layer(&mut self, key: LayerKey, layer: Layer) {
442 self.layers.insert(key, layer);
443 }
444
445 pub fn remove_layer(&mut self, key: &LayerKey) -> Option<Layer> {
447 self.layers.remove(key)
448 }
449
450 pub fn layer_keys(&self) -> Vec<&LayerKey> {
452 self.layers.keys().collect()
453 }
454
455 pub fn fpca(&self) -> Option<&FpcaLayer> {
459 match self.layers.get(&LayerKey::Fpca)? {
460 Layer::Fpca(l) => Some(l),
461 _ => None,
462 }
463 }
464
465 pub fn distances(&self) -> Option<&DistancesLayer> {
467 match self.layers.get(&LayerKey::Distances)? {
468 Layer::Distances(l) => Some(l),
469 _ => None,
470 }
471 }
472
473 pub fn alignment(&self) -> Option<&AlignmentLayer> {
475 match self.layers.get(&LayerKey::Alignment)? {
476 Layer::Alignment(l) => Some(l),
477 _ => None,
478 }
479 }
480
481 pub fn regression(&self) -> Option<&RegressionLayer> {
483 match self.layers.get(&LayerKey::Regression)? {
484 Layer::Regression(l) => Some(l),
485 _ => None,
486 }
487 }
488
489 pub fn clusters(&self) -> Option<&ClusterLayer> {
491 match self.layers.get(&LayerKey::Clusters)? {
492 Layer::Clusters(l) => Some(l),
493 _ => None,
494 }
495 }
496
497 pub fn depth(&self) -> Option<&DepthLayer> {
499 match self.layers.get(&LayerKey::Depth)? {
500 Layer::Depth(l) => Some(l),
501 _ => None,
502 }
503 }
504
505 pub fn outliers(&self) -> Option<&OutlierLayer> {
507 match self.layers.get(&LayerKey::Outliers)? {
508 Layer::Outliers(l) => Some(l),
509 _ => None,
510 }
511 }
512
513 pub fn n_obs(&self) -> usize {
517 if let Some(c) = &self.curves {
518 return c.nrows();
519 }
520 if let Some(t) = &self.tabular {
521 return t.nrows();
522 }
523 self.scalar_vars.first().map_or(0, |v| v.values.len())
524 }
525
526 pub fn n_points(&self) -> usize {
528 self.argvals.as_ref().map_or(0, |a| a.len())
529 }
530
531 pub fn add_scalar(&mut self, name: impl Into<String>, values: Vec<f64>) {
533 self.scalar_vars.push(NamedVec {
534 name: name.into(),
535 values,
536 });
537 }
538
539 pub fn get_scalar(&self, name: &str) -> Option<&[f64]> {
541 self.scalar_vars
542 .iter()
543 .find(|v| v.name == name)
544 .map(|v| v.values.as_slice())
545 }
546}
547
548#[cfg(test)]
551mod tests {
552 use super::*;
553
554 #[test]
555 fn from_curves_basic() {
556 let fd = FdaData::from_curves(
557 FdMatrix::zeros(10, 50),
558 (0..50).map(|i| i as f64 / 49.0).collect(),
559 );
560 assert_eq!(fd.n_obs(), 10);
561 assert_eq!(fd.n_points(), 50);
562 assert!(fd.require_curves().is_ok());
563 assert!(!fd.has_layer(&LayerKey::Fpca));
564 }
565
566 #[test]
567 fn add_and_retrieve_layers() {
568 let mut fd = FdaData::from_curves(
569 FdMatrix::zeros(5, 20),
570 (0..20).map(|i| i as f64 / 19.0).collect(),
571 );
572
573 fd.set_layer(
574 LayerKey::Depth,
575 Layer::Depth(DepthLayer {
576 scores: vec![0.5; 5],
577 method: "fraiman_muniz".into(),
578 }),
579 );
580
581 assert!(fd.has_layer(&LayerKey::Depth));
582 assert!(!fd.has_layer(&LayerKey::Fpca));
583 assert!(fd.depth().is_some());
584 assert_eq!(fd.depth().unwrap().scores.len(), 5);
585 assert_eq!(fd.layer_keys().len(), 1);
586 }
587
588 #[test]
589 fn require_missing_layer_errors() {
590 let fd = FdaData::from_curves(FdMatrix::zeros(3, 10), vec![0.0; 10]);
591 assert!(fd.require_layer(&LayerKey::Fpca).is_err());
592 }
593
594 #[test]
595 fn scalar_vars() {
596 let mut fd = FdaData::empty();
597 fd.add_scalar("height", vec![170.0, 180.0, 165.0]);
598 assert_eq!(fd.get_scalar("height").unwrap(), &[170.0, 180.0, 165.0]);
599 assert!(fd.get_scalar("weight").is_none());
600 assert_eq!(fd.n_obs(), 3);
601 }
602
603 #[test]
604 fn multiple_layers_compose() {
605 let mut fd = FdaData::from_curves(FdMatrix::zeros(10, 30), vec![0.0; 30]);
606
607 fd.set_layer(
608 LayerKey::Depth,
609 Layer::Depth(DepthLayer {
610 scores: vec![0.5; 10],
611 method: "fm".into(),
612 }),
613 );
614 fd.set_layer(
615 LayerKey::Outliers,
616 Layer::Outliers(OutlierLayer {
617 flags: vec![false; 10],
618 threshold: 0.1,
619 method: "lrt".into(),
620 mei: None,
621 mbd: None,
622 magnitude: None,
623 shape: None,
624 }),
625 );
626 fd.set_layer(
627 LayerKey::Distances,
628 Layer::Distances(DistancesLayer {
629 dist_mat: FdMatrix::zeros(10, 10),
630 method: "elastic".into(),
631 }),
632 );
633
634 assert_eq!(fd.layer_keys().len(), 3);
635 assert!(fd.depth().is_some());
636 assert!(fd.outliers().is_some());
637 assert!(fd.distances().is_some());
638 }
639}