fdars_core/boosting_regression/mod.rs
1//! Component-wise gradient boosting and Bayesian regression for functional responses.
2//!
3//! Implements REG-06: FDboost-style penalized functional base-learner boosting,
4//! GAMLSS distributional boosting, conjugate Gibbs Bayesian FOSR, and stability selection.
5//!
6//! # Methods
7//!
8//! - [`boost_fosr`] — Component-wise boosted function-on-scalar regression (REG-06-01)
9//! - [`boost_fofr`] — Component-wise boosted function-on-function regression (REG-06-02)
10//! - [`gamlss_fosr`] — GAMLSS distributional boosting: location + scale (REG-06-03)
11//! - [`bayesian_fosr`] — Bayesian FOSR via conjugate Gibbs sampler (REG-06-04)
12//! - [`stability_selection`] — FDboost-style stability selection (REG-06-05)
13//!
14//! # References
15//!
16//! Hothorn et al. (2010). Model-Based Boosting. *Journal of Statistical Software*.
17//! Hofner et al. (2016). gamboostLSS. *Journal of Statistical Software*, 74(1).
18//! Jiang et al. (2025). arXiv:2505.05633 (Bayesian FoSR).
19//! Meinshausen & Bühlmann (2010). Stability Selection. *JRSS-B*, 72(4).
20//!
21//! Divergences from R baselines (FDboost 1.1-4, refund, stabs) documented per function.
22
23use crate::matrix::FdMatrix;
24
25pub mod bayesian;
26pub mod boost_fofr;
27pub mod boost_fosr;
28pub mod gamlss;
29pub mod stability;
30
31// ---------------------------------------------------------------------------
32// Config structs
33// ---------------------------------------------------------------------------
34
35/// Configuration for component-wise gradient boosting (FOSR, FoFR, GAMLSS, stability).
36///
37/// All base-learners use the same `nbasis`, `order`, `lfd_order`, and `lambda`
38/// to ensure equal effective degrees of freedom, preventing selection bias toward
39/// more flexible learners (see Pitfall 4 in RESEARCH.md).
40///
41/// **Divergence from FDboost:** Fixed `nu` and `mstop` rather than CV-based early stopping;
42/// the GCV path is tracked for diagnostic purposes but not used for stopping.
43#[derive(Debug, Clone, PartialEq)]
44pub struct BoostingConfig {
45 /// Number of boosting iterations (must be ≥ 1).
46 pub mstop: usize,
47 /// Learning rate ν ∈ (0, 1] (FDboost default: 0.1).
48 pub nu: f64,
49 /// Number of B-spline basis functions per base-learner (must be ≥ 4).
50 ///
51 /// The actual number of basis functions is `nknots + order` where
52 /// `nknots = nbasis - order`. With `order = 4` (cubic), `nbasis = 10`
53 /// gives 6 interior knots.
54 pub nbasis: usize,
55 /// B-spline order (typically 4 for cubic splines).
56 pub order: usize,
57 /// Penalty derivative order (typically 2 for roughness).
58 pub lfd_order: usize,
59 /// Smoothing parameter λ > 0 for penalized base-learners.
60 pub lambda: f64,
61 /// Number of predictor FPC components for FoFR base-learners (REG-06-02).
62 pub ncomp_x: usize,
63 /// RNG seed (used by stability selection and future extensions; unused in pure boosting).
64 pub seed: u64,
65}
66
67impl Default for BoostingConfig {
68 /// FDboost-convention defaults: `mstop = 100`, `nu = 0.1`, cubic (`order = 4`)
69 /// B-spline base-learners with `nbasis = 10`, second-derivative penalty
70 /// (`lfd_order = 2`), `lambda = 1.0`, `ncomp_x = 3`, `seed = 0`.
71 fn default() -> Self {
72 Self {
73 mstop: 100,
74 nu: 0.1,
75 nbasis: 10,
76 order: 4,
77 lfd_order: 2,
78 lambda: 1.0,
79 ncomp_x: 3,
80 seed: 0,
81 }
82 }
83}
84
85/// Configuration for the Bayesian FOSR Gibbs sampler (REG-06-04).
86///
87/// Uses conjugate Normal-Inverse-Gamma priors. Defaults match the weakly-informative
88/// settings recommended by Jiang et al. (2025): `τ² = 100`, `IG(0.001, 0.001)`.
89///
90/// **Divergence from refund:** refund's Bayesian FOSR uses spline basis priors;
91/// this implementation uses FPCA score compression via `fdata_to_pc_1d` for
92/// simplicity and zero new dependencies.
93#[derive(Debug, Clone, PartialEq)]
94pub struct BayesianConfig {
95 /// Number of FPC components for score compression (must be ≥ 1).
96 pub ncomp: usize,
97 /// Prior variance τ² on FPC-space coefficients (default: 100.0).
98 ///
99 /// Large `τ²` gives a weakly informative prior; very small values are dogmatic.
100 pub tau2: f64,
101 /// Inverse-Gamma prior shape a₀ (default: 0.001 — weakly informative).
102 pub ig_a0: f64,
103 /// Inverse-Gamma prior rate b₀ (default: 0.001 — weakly informative).
104 pub ig_b0: f64,
105 /// Number of Gibbs iterations retained after burn-in (must be ≥ 1).
106 pub n_iter: usize,
107 /// Burn-in iterations discarded (must be < `n_iter` iterations will run).
108 pub burn_in: usize,
109 /// Thinning interval — keep every `thin`-th draw (must be ≥ 1).
110 pub thin: usize,
111 /// RNG seed — chain is fully deterministic: `StdRng::seed_from_u64(seed)`.
112 pub seed: u64,
113}
114
115impl Default for BayesianConfig {
116 /// Weakly-informative defaults per Jiang et al. (2025): `tau2 = 100.0`,
117 /// `IG(0.001, 0.001)`, with `ncomp = 4`, `n_iter = 400`, `burn_in = 200`,
118 /// `thin = 1` (these mirror `bayesian::tests::default_config`), and `seed = 0`
119 /// as the deterministic default (the test config uses its own seed).
120 fn default() -> Self {
121 Self {
122 ncomp: 4,
123 tau2: 100.0,
124 ig_a0: 0.001,
125 ig_b0: 0.001,
126 n_iter: 400,
127 burn_in: 200,
128 thin: 1,
129 seed: 0,
130 }
131 }
132}
133
134/// Configuration for FDboost-style stability selection (REG-06-05).
135///
136/// Implements the Meinshausen-Bühlmann subsampling scheme with ⌊n/2⌋ rows
137/// per replicate. The PFER bound `E[V] ≤ q² / ((2·π_thr − 1)·p)` is reported
138/// as an informational diagnostic.
139#[derive(Debug, Clone, PartialEq)]
140pub struct StabilityConfig {
141 /// Number of resamples B (must be ≥ 1; default: 100).
142 pub n_resamples: usize,
143 /// Selection threshold π ∈ (0.5, 1.0] (default: 0.9).
144 ///
145 /// Base-learner j is declared "stable" if its selection frequency ≥ `pi_thr`.
146 pub pi_thr: f64,
147 /// Base RNG seed; replicate `b` uses `seed.wrapping_add(b as u64)` for isolation.
148 pub seed: u64,
149}
150
151impl Default for StabilityConfig {
152 /// Meinshausen-Bühlmann defaults: `n_resamples = 100`, `pi_thr = 0.9`,
153 /// `seed = 0`.
154 fn default() -> Self {
155 Self {
156 n_resamples: 100,
157 pi_thr: 0.9,
158 seed: 0,
159 }
160 }
161}
162
163// ---------------------------------------------------------------------------
164// Result structs
165// ---------------------------------------------------------------------------
166
167/// Result of component-wise boosted function-on-scalar regression (REG-06-01).
168///
169/// Follows the `FosrResult` field convention; adds the boosting path diagnostics
170/// (`selected_learners`, `gcv_path`, `mstop`, `nu`).
171///
172/// **Divergence from FDboost:** fixed `mstop` / fixed `nu`; no CV-based early stopping.
173/// GCV path is tracked for post-hoc diagnostics only (see `gcv_path`).
174#[derive(Debug, Clone, PartialEq)]
175#[non_exhaustive]
176pub struct BoostFosrResult {
177 /// Intercept function F₀(t) = Ȳ(t) (pointwise mean of Y, length m_t).
178 pub intercept: Vec<f64>,
179 /// Accumulated coefficient functions β_j(t) per predictor (p × m_t).
180 ///
181 /// Row j holds the total contribution of base-learner j across all iterations
182 /// where it was selected, scaled by ν.
183 pub beta: FdMatrix,
184 /// Fitted functional values F̂(xᵢ, t) = Σ contributions (n × m_t).
185 pub fitted: FdMatrix,
186 /// Residual curves Y − F̂ (n × m_t).
187 pub residuals: FdMatrix,
188 /// Pointwise R²(t) at each response grid point (length m_t).
189 pub r_squared_t: Vec<f64>,
190 /// Integrated R² (scalar summary).
191 pub r_squared: f64,
192 /// Number of boosting iterations used.
193 pub mstop: usize,
194 /// Learning rate ν used.
195 pub nu: f64,
196 /// Index j* of the base-learner selected at each boosting iteration (length mstop).
197 pub selected_learners: Vec<usize>,
198 /// ‖residual‖_F² recorded after each iteration (length mstop).
199 ///
200 /// Should be non-increasing for L2 loss (use for path diagnostics / GCV).
201 pub gcv_path: Vec<f64>,
202}
203
204/// Result of component-wise boosted function-on-function regression (REG-06-02).
205///
206/// Functional predictors are compressed via FPCA score projection; the boosting
207/// core operates on the resulting scalar design matrices (bfpc variant).
208///
209/// **Divergence from FDboost:** uses FPC score compression (`fdata_to_pc_1d`) rather
210/// than FDboost's `bsignal` B-spline joint expansion. Simpler and dependency-free.
211#[derive(Debug, Clone, PartialEq)]
212#[non_exhaustive]
213pub struct BoostFofrResult {
214 /// Intercept function F₀(t) = Ȳ(t) (length m_y).
215 pub intercept: Vec<f64>,
216 /// Fitted response curves (n × m_y).
217 pub fitted: FdMatrix,
218 /// Residual curves (n × m_y).
219 pub residuals: FdMatrix,
220 /// Pointwise R²(t) at each response grid point (length m_y).
221 pub r_squared_t: Vec<f64>,
222 /// Overall R².
223 pub r_squared: f64,
224 /// FPCA result for each functional predictor (one per predictor).
225 pub fpca_x: Vec<crate::regression::FpcaResult>,
226 /// Accumulated FPC-space score coefficients per predictor (Vec[j] is K_j × m_y).
227 pub score_coefs: Vec<FdMatrix>,
228 /// Reconstructed coefficient surfaces β_j(s,t) per predictor (Vec[j] is m_x × m_y).
229 pub beta_surfaces: Vec<FdMatrix>,
230 /// Index j* of the base-learner selected at each boosting iteration (length mstop).
231 pub selected_learners: Vec<usize>,
232 /// ‖residual‖_F² per boosting iteration (length mstop).
233 pub gcv_path: Vec<f64>,
234 /// Number of boosting iterations used.
235 pub mstop: usize,
236 /// Learning rate ν used.
237 pub nu: f64,
238}
239
240/// Result of GAMLSS-style distributional functional regression (REG-06-03).
241///
242/// Models a Gaussian functional response Y(t) with location μ(t) and scale σ(t).
243/// Cyclic component-wise boosting alternates between boosting μ and log-σ.
244///
245/// **Divergence from gamboostLSS:** uses cyclic rather than noncyclic (non-cyclic
246/// per-iteration selection is superior for variable selection but more complex).
247/// Links: identity for μ, log for σ.
248#[derive(Debug, Clone, PartialEq)]
249#[non_exhaustive]
250pub struct GamlssResult {
251 /// Fitted location μ̂(t) per observation (n × m_t).
252 pub mu_fitted: FdMatrix,
253 /// Fitted scale σ̂(t) per observation (n × m_t); always positive.
254 pub sigma_fitted: FdMatrix,
255 /// Intercept for the μ model: F̂_μ,₀(t) = Ȳ(t) (length m_t).
256 pub mu_intercept: Vec<f64>,
257 /// Intercept for the log-σ model: 0 → exp(0) = 1 (length m_t).
258 pub sigma_intercept: Vec<f64>,
259 /// Accumulated μ coefficient functions (p × m_t).
260 pub mu_beta: FdMatrix,
261 /// Accumulated log-σ coefficient functions (p × m_t).
262 pub sigma_beta: FdMatrix,
263 /// Final Gaussian log-likelihood at convergence.
264 pub log_likelihood: f64,
265 /// Log-likelihood per cyclic iteration (length mstop).
266 pub ll_path: Vec<f64>,
267 /// Number of cyclic boosting iterations used.
268 pub mstop: usize,
269 /// Learning rate ν used.
270 pub nu: f64,
271}
272
273/// Result of Bayesian function-on-scalar regression via conjugate Gibbs (REG-06-04).
274///
275/// Posterior summaries are computed from thinned post-burn-in draws. Credible bands
276/// are pointwise (not simultaneous) quantiles over the retained draws.
277///
278/// **Divergence from refund:** uses FPCA score compression via `fdata_to_pc_1d`
279/// rather than spline basis priors. Pointwise credible bands only (no simultaneous bands).
280#[derive(Debug, Clone, PartialEq)]
281#[non_exhaustive]
282pub struct BayesianFosrResult {
283 /// Posterior mean coefficient functions β̄(t) (p × m_t).
284 pub beta_mean: FdMatrix,
285 /// Pointwise 2.5% credible band (p × m_t).
286 pub beta_lower: FdMatrix,
287 /// Pointwise 97.5% credible band (p × m_t).
288 pub beta_upper: FdMatrix,
289 /// Posterior-mean fitted values (n × m_t).
290 pub fitted: FdMatrix,
291 /// Posterior-mean residuals (n × m_t).
292 pub residuals: FdMatrix,
293 /// Posterior mean σ²(t) across the response grid (length m_t).
294 pub sigma2_mean: Vec<f64>,
295 /// Number of Gibbs iterations retained (after burn-in, before thinning).
296 pub n_iter: usize,
297 /// Burn-in iterations discarded.
298 pub burn_in: usize,
299 /// Thinning interval used.
300 pub thin: usize,
301 /// FPC components used for score compression.
302 pub ncomp: usize,
303}
304
305/// Result of FDboost-style stability selection (REG-06-05).
306///
307/// Aggregates base-learner selection frequencies over B subsamples of size ⌊n/2⌋.
308/// The PFER bound is informational: `E[V] ≤ q² / ((2·π_thr − 1)·p)`.
309#[derive(Debug, Clone, PartialEq)]
310#[non_exhaustive]
311pub struct StabilityResult {
312 /// Selection frequency π̂[j] ∈ [0, 1] for each base-learner j = 0..p.
313 pub selection_freq: Vec<f64>,
314 /// Indices j where π̂[j] ≥ pi_thr (the "stable set").
315 pub stable_set: Vec<usize>,
316 /// Threshold π used.
317 pub pi_thr: f64,
318 /// PFER upper bound: `q² / ((2·pi_thr − 1)·p)` where q = mean per-subsample selection count.
319 pub pfer_bound: f64,
320 /// Number of resamples B used.
321 pub n_resamples: usize,
322}
323
324// ---------------------------------------------------------------------------
325// Barrel re-exports
326// ---------------------------------------------------------------------------
327
328pub use self::bayesian::bayesian_fosr;
329pub use self::boost_fofr::boost_fofr;
330pub use self::boost_fosr::boost_fosr;
331pub use self::gamlss::gamlss_fosr;
332pub use self::stability::stability_selection;
333
334#[cfg(test)]
335mod tests {
336 use super::{BayesianConfig, BoostingConfig, StabilityConfig};
337
338 #[test]
339 fn config_defaults_match_documented_values() {
340 assert_eq!(
341 BoostingConfig::default(),
342 BoostingConfig {
343 mstop: 100,
344 nu: 0.1,
345 nbasis: 10,
346 order: 4,
347 lfd_order: 2,
348 lambda: 1.0,
349 ncomp_x: 3,
350 seed: 0,
351 }
352 );
353 assert_eq!(
354 BayesianConfig::default(),
355 BayesianConfig {
356 ncomp: 4,
357 tau2: 100.0,
358 ig_a0: 0.001,
359 ig_b0: 0.001,
360 n_iter: 400,
361 burn_in: 200,
362 thin: 1,
363 seed: 0,
364 }
365 );
366 assert_eq!(
367 StabilityConfig::default(),
368 StabilityConfig {
369 n_resamples: 100,
370 pi_thr: 0.9,
371 seed: 0,
372 }
373 );
374 }
375}