1use crate::error::FdarError;
20use crate::linalg::cholesky_solve as linalg_cholesky_solve;
21use crate::matrix::FdMatrix;
22use crate::regression::{FpcaResult, PlsResult};
23
24mod bootstrap;
25mod cv;
26mod fregre_lm;
27mod logistic;
28mod multi;
29mod nonparametric;
30mod pls;
31mod robust;
32#[cfg(test)]
33mod tests;
34
35pub use bootstrap::{bootstrap_ci_fregre_lm, bootstrap_ci_functional_logistic};
37pub use cv::{fregre_basis_cv, fregre_np_cv};
38pub use fregre_lm::{fregre_cv, fregre_lm, model_selection_ncomp, predict_fregre_lm};
39pub use logistic::{functional_logistic, predict_functional_logistic};
40pub use multi::{fregre_lm_multi, fregre_lm_multi_cv, predict_fregre_lm_multi, MultiCvResult};
41pub use nonparametric::{
42 fregre_np_from_distances, fregre_np_mixed, predict_fregre_np, predict_fregre_np_from_distances,
43};
44pub use pls::{fregre_pls, predict_fregre_pls};
45pub use robust::{fregre_huber, fregre_l1, predict_fregre_robust};
46
47#[derive(Debug, Clone, PartialEq)]
53#[non_exhaustive]
54pub struct FregreLmResult {
55 pub intercept: f64,
57 pub beta_t: Vec<f64>,
59 pub beta_se: Vec<f64>,
61 pub gamma: Vec<f64>,
63 pub fitted_values: Vec<f64>,
65 pub residuals: Vec<f64>,
67 pub r_squared: f64,
69 pub r_squared_adj: f64,
71 pub std_errors: Vec<f64>,
73 pub ncomp: usize,
75 pub fpca: FpcaResult,
77 pub coefficients: Vec<f64>,
79 pub residual_se: f64,
81 pub gcv: f64,
83 pub aic: f64,
85 pub bic: f64,
87}
88
89#[derive(Debug, Clone, PartialEq)]
91#[non_exhaustive]
92pub struct FregreNpResult {
93 pub fitted_values: Vec<f64>,
95 pub residuals: Vec<f64>,
97 pub r_squared: f64,
99 pub h_func: f64,
101 pub h_scalar: f64,
103 pub cv_error: f64,
105}
106
107#[derive(Debug, Clone, PartialEq)]
109#[non_exhaustive]
110pub struct FregreRobustResult {
111 pub intercept: f64,
113 pub beta_t: Vec<f64>,
115 pub fitted_values: Vec<f64>,
117 pub residuals: Vec<f64>,
119 pub coefficients: Vec<f64>,
121 pub ncomp: usize,
123 pub fpca: FpcaResult,
125 pub iterations: usize,
127 pub converged: bool,
129 pub weights: Vec<f64>,
131 pub r_squared: f64,
133}
134
135#[derive(Debug, Clone, PartialEq)]
137#[non_exhaustive]
138pub struct FunctionalLogisticResult {
139 pub intercept: f64,
141 pub beta_t: Vec<f64>,
143 pub beta_se: Vec<f64>,
145 pub gamma: Vec<f64>,
147 pub probabilities: Vec<f64>,
149 pub predicted_classes: Vec<usize>,
151 pub ncomp: usize,
153 pub accuracy: f64,
155 pub std_errors: Vec<f64>,
157 pub coefficients: Vec<f64>,
159 pub log_likelihood: f64,
161 pub iterations: usize,
163 pub fpca: FpcaResult,
165 pub aic: f64,
167 pub bic: f64,
169}
170
171#[derive(Debug, Clone, PartialEq)]
173#[non_exhaustive]
174pub struct FregreCvResult {
175 pub k_values: Vec<usize>,
177 pub cv_errors: Vec<f64>,
179 pub optimal_k: usize,
181 pub min_cv_error: f64,
183 pub oof_predictions: Vec<f64>,
185 pub fold_assignments: Vec<usize>,
187 pub fold_errors: Vec<f64>,
189}
190
191#[derive(Debug, Clone, PartialEq)]
193#[non_exhaustive]
194pub struct PlsRegressionResult {
195 pub intercept: f64,
197 pub beta_t: Vec<f64>,
199 pub gamma: Vec<f64>,
201 pub fitted_values: Vec<f64>,
203 pub residuals: Vec<f64>,
205 pub r_squared: f64,
207 pub r_squared_adj: f64,
209 pub ncomp: usize,
211 pub pls: PlsResult,
213 pub coefficients: Vec<f64>,
215 pub residual_se: f64,
217 pub aic: f64,
219 pub bic: f64,
221}
222
223#[derive(Debug, Clone, PartialEq)]
225#[non_exhaustive]
226pub struct MultiFregreLmResult {
227 pub intercept: f64,
229 pub beta_t: Vec<Vec<f64>>,
231 pub gamma: Vec<f64>,
233 pub fitted_values: Vec<f64>,
235 pub residuals: Vec<f64>,
237 pub r_squared: f64,
239 pub r_squared_adj: f64,
241 pub ncomp: Vec<usize>,
243 pub fpcas: Vec<FpcaResult>,
245 pub coefficients: Vec<f64>,
247 pub residual_se: f64,
249 pub aic: f64,
251 pub bic: f64,
253}
254
255#[derive(Debug, Clone, Copy, PartialEq)]
257pub enum SelectionCriterion {
258 Aic,
260 Bic,
262 Gcv,
264}
265
266#[derive(Debug, Clone, PartialEq)]
268#[non_exhaustive]
269pub struct ModelSelectionResult {
270 pub best_ncomp: usize,
272 pub criteria: Vec<(usize, f64, f64, f64)>,
274}
275
276#[derive(Debug, Clone, PartialEq)]
278#[non_exhaustive]
279pub struct BootstrapCiResult {
280 pub lower: Vec<f64>,
282 pub upper: Vec<f64>,
284 pub center: Vec<f64>,
286 pub sim_lower: Vec<f64>,
288 pub sim_upper: Vec<f64>,
290 pub n_boot_success: usize,
292}
293
294#[derive(Debug, Clone, PartialEq)]
296#[non_exhaustive]
297pub struct FregreBasisCvResult {
298 pub optimal_lambda: f64,
300 pub cv_errors: Vec<f64>,
302 pub cv_se: Vec<f64>,
304 pub lambda_values: Vec<f64>,
306 pub min_cv_error: f64,
308}
309
310#[derive(Debug, Clone, PartialEq)]
312#[non_exhaustive]
313pub struct FregreNpCvResult {
314 pub optimal_h: f64,
316 pub cv_errors: Vec<f64>,
318 pub cv_se: Vec<f64>,
320 pub h_values: Vec<f64>,
322 pub min_cv_error: f64,
324}
325
326pub(crate) use crate::linalg::cholesky_factor;
333pub(crate) use crate::linalg::cholesky_forward_back;
334pub(crate) use crate::linalg::compute_xtx;
335
336fn compute_xty(x: &FdMatrix, y: &[f64]) -> Vec<f64> {
338 let (n, p) = x.shape();
339 (0..p)
340 .map(|k| {
341 let mut s = 0.0;
342 for i in 0..n {
343 s += x[(i, k)] * y[i];
344 }
345 s
346 })
347 .collect()
348}
349
350pub(super) fn cholesky_solve(a: &[f64], b: &[f64], p: usize) -> Result<Vec<f64>, FdarError> {
352 linalg_cholesky_solve(a, b, p)
353}
354
355pub(crate) fn compute_hat_diagonal(x: &FdMatrix, l: &[f64]) -> Vec<f64> {
357 let (n, p) = x.shape();
358 let mut hat_diag = vec![0.0; n];
359 for i in 0..n {
360 let mut v = vec![0.0; p];
361 for j in 0..p {
362 v[j] = x[(i, j)];
363 for k in 0..j {
364 v[j] -= l[j * p + k] * v[k];
365 }
366 v[j] /= l[j * p + j];
367 }
368 hat_diag[i] = v.iter().map(|vi| vi * vi).sum();
369 }
370 hat_diag
371}
372
373fn compute_ols_std_errors(l: &[f64], p: usize, sigma2: f64) -> Vec<f64> {
375 let mut se = vec![0.0; p];
376 for j in 0..p {
377 let mut v = vec![0.0; p];
378 v[j] = 1.0;
379 for k in 0..p {
380 for kk in 0..k {
381 v[k] -= l[k * p + kk] * v[kk];
382 }
383 v[k] /= l[k * p + k];
384 }
385 se[j] = (sigma2 * v.iter().map(|vi| vi * vi).sum::<f64>()).sqrt();
386 }
387 se
388}
389
390fn validate_fregre_inputs(
397 n: usize,
398 m: usize,
399 y: &[f64],
400 scalar_covariates: Option<&FdMatrix>,
401) -> Result<(), FdarError> {
402 if n < 3 {
403 return Err(FdarError::InvalidDimension {
404 parameter: "data",
405 expected: "at least 3 rows".to_string(),
406 actual: format!("{n}"),
407 });
408 }
409 if m == 0 {
410 return Err(FdarError::InvalidDimension {
411 parameter: "data",
412 expected: "at least 1 column".to_string(),
413 actual: "0".to_string(),
414 });
415 }
416 if y.len() != n {
417 return Err(FdarError::InvalidDimension {
418 parameter: "y",
419 expected: format!("{n}"),
420 actual: format!("{}", y.len()),
421 });
422 }
423 if let Some(sc) = scalar_covariates {
424 if sc.nrows() != n {
425 return Err(FdarError::InvalidDimension {
426 parameter: "scalar_covariates",
427 expected: format!("{n} rows"),
428 actual: format!("{} rows", sc.nrows()),
429 });
430 }
431 }
432 Ok(())
433}
434
435fn resolve_ncomp(
437 ncomp: usize,
438 data: &FdMatrix,
439 y: &[f64],
440 scalar_covariates: Option<&FdMatrix>,
441 n: usize,
442 m: usize,
443) -> Result<usize, FdarError> {
444 if ncomp == 0 {
445 let cv = fregre_cv(data, y, scalar_covariates, 1, m.min(n - 1).min(20), 5)?;
446 Ok(cv.optimal_k)
447 } else {
448 Ok(ncomp.min(n - 1).min(m))
449 }
450}
451
452pub(crate) fn build_design_matrix(
453 fpca_scores: &FdMatrix,
454 ncomp: usize,
455 scalar_covariates: Option<&FdMatrix>,
456 n: usize,
457) -> FdMatrix {
458 let p_scalar = scalar_covariates.map_or(0, super::matrix::FdMatrix::ncols);
459 let p_total = 1 + ncomp + p_scalar;
460 let mut design = FdMatrix::zeros(n, p_total);
461 for i in 0..n {
462 design[(i, 0)] = 1.0;
463 for k in 0..ncomp {
464 design[(i, 1 + k)] = fpca_scores[(i, k)];
465 }
466 if let Some(sc) = scalar_covariates {
467 for j in 0..p_scalar {
468 design[(i, 1 + ncomp + j)] = sc[(i, j)];
469 }
470 }
471 }
472 design
473}
474
475fn recover_beta_t(fpc_coeffs: &[f64], rotation: &FdMatrix, m: usize) -> Vec<f64> {
477 let ncomp = fpc_coeffs.len();
478 let mut beta_t = vec![0.0; m];
479 for k in 0..ncomp {
480 for j in 0..m {
481 beta_t[j] += fpc_coeffs[k] * rotation[(j, k)];
482 }
483 }
484 beta_t
485}
486
487fn compute_beta_se(gamma_se: &[f64], rotation: &FdMatrix, m: usize) -> Vec<f64> {
491 let ncomp = gamma_se.len();
492 let mut beta_se = vec![0.0; m];
493 for j in 0..m {
494 let mut var_j = 0.0;
495 for k in 0..ncomp {
496 var_j += rotation[(j, k)].powi(2) * gamma_se[k].powi(2);
497 }
498 beta_se[j] = var_j.sqrt();
499 }
500 beta_se
501}
502
503fn compute_fitted(design: &FdMatrix, coeffs: &[f64]) -> Vec<f64> {
505 let (n, p) = design.shape();
506 (0..n)
507 .map(|i| {
508 let mut yhat = 0.0;
509 for j in 0..p {
510 yhat += design[(i, j)] * coeffs[j];
511 }
512 yhat
513 })
514 .collect()
515}
516
517fn compute_r_squared(y: &[f64], residuals: &[f64], p_total: usize) -> (f64, f64) {
519 let n = y.len();
520 let y_mean = y.iter().sum::<f64>() / n as f64;
521 let ss_tot: f64 = y.iter().map(|&yi| (yi - y_mean).powi(2)).sum();
522 let ss_res: f64 = residuals.iter().map(|r| r * r).sum();
523 let r_squared = if ss_tot > 0.0 {
524 1.0 - ss_res / ss_tot
525 } else {
526 0.0
527 };
528 let df_model = (p_total - 1) as f64;
529 let r_squared_adj = if n as f64 - df_model - 1.0 > 0.0 {
530 1.0 - (1.0 - r_squared) * (n as f64 - 1.0) / (n as f64 - df_model - 1.0)
531 } else {
532 r_squared
533 };
534 (r_squared, r_squared_adj)
535}
536
537fn ols_solve(x: &FdMatrix, y: &[f64]) -> Result<(Vec<f64>, Vec<f64>), FdarError> {
544 let (n, p) = x.shape();
545 if n < p || p == 0 {
546 return Err(FdarError::InvalidDimension {
547 parameter: "design matrix",
548 expected: format!("n >= p and p > 0 (p={p})"),
549 actual: format!("n={n}, p={p}"),
550 });
551 }
552 let xtx = compute_xtx(x);
553 let xty = compute_xty(x, y);
554 let l = cholesky_factor(&xtx, p)?;
555 let b = cholesky_forward_back(&l, &xty, p);
556 let hat_diag = compute_hat_diagonal(x, &l);
557 Ok((b, hat_diag))
558}
559
560pub(crate) fn sigmoid(x: f64) -> f64 {
562 if x >= 0.0 {
563 1.0 / (1.0 + (-x).exp())
564 } else {
565 let ex = x.exp();
566 ex / (1.0 + ex)
567 }
568}
569
570impl FregreLmResult {
575 pub fn predict(&self, new_data: &FdMatrix, new_scalar: Option<&FdMatrix>) -> Vec<f64> {
577 predict_fregre_lm(self, new_data, new_scalar)
578 }
579}
580
581impl FregreRobustResult {
582 pub fn predict(&self, new_data: &FdMatrix, new_scalar: Option<&FdMatrix>) -> Vec<f64> {
584 predict_fregre_robust(self, new_data, new_scalar)
585 }
586}
587
588impl FunctionalLogisticResult {
589 pub fn predict(&self, new_data: &FdMatrix, new_scalar: Option<&FdMatrix>) -> Vec<f64> {
591 predict_functional_logistic(self, new_data, new_scalar)
592 }
593}
594
595impl MultiFregreLmResult {
596 pub fn predict(
602 &self,
603 new_predictors: &[&FdMatrix],
604 new_scalar: Option<&FdMatrix>,
605 ) -> Result<Vec<f64>, FdarError> {
606 predict_fregre_lm_multi(self, new_predictors, new_scalar)
607 }
608}