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
67/// Configuration for the Bayesian FOSR Gibbs sampler (REG-06-04).
68///
69/// Uses conjugate Normal-Inverse-Gamma priors. Defaults match the weakly-informative
70/// settings recommended by Jiang et al. (2025): `τ² = 100`, `IG(0.001, 0.001)`.
71///
72/// **Divergence from refund:** refund's Bayesian FOSR uses spline basis priors;
73/// this implementation uses FPCA score compression via `fdata_to_pc_1d` for
74/// simplicity and zero new dependencies.
75#[derive(Debug, Clone, PartialEq)]
76pub struct BayesianConfig {
77 /// Number of FPC components for score compression (must be ≥ 1).
78 pub ncomp: usize,
79 /// Prior variance τ² on FPC-space coefficients (default: 100.0).
80 ///
81 /// Large `τ²` gives a weakly informative prior; very small values are dogmatic.
82 pub tau2: f64,
83 /// Inverse-Gamma prior shape a₀ (default: 0.001 — weakly informative).
84 pub ig_a0: f64,
85 /// Inverse-Gamma prior rate b₀ (default: 0.001 — weakly informative).
86 pub ig_b0: f64,
87 /// Number of Gibbs iterations retained after burn-in (must be ≥ 1).
88 pub n_iter: usize,
89 /// Burn-in iterations discarded (must be < `n_iter` iterations will run).
90 pub burn_in: usize,
91 /// Thinning interval — keep every `thin`-th draw (must be ≥ 1).
92 pub thin: usize,
93 /// RNG seed — chain is fully deterministic: `StdRng::seed_from_u64(seed)`.
94 pub seed: u64,
95}
96
97/// Configuration for FDboost-style stability selection (REG-06-05).
98///
99/// Implements the Meinshausen-Bühlmann subsampling scheme with ⌊n/2⌋ rows
100/// per replicate. The PFER bound `E[V] ≤ q² / ((2·π_thr − 1)·p)` is reported
101/// as an informational diagnostic.
102#[derive(Debug, Clone, PartialEq)]
103pub struct StabilityConfig {
104 /// Number of resamples B (must be ≥ 1; default: 100).
105 pub n_resamples: usize,
106 /// Selection threshold π ∈ (0.5, 1.0] (default: 0.9).
107 ///
108 /// Base-learner j is declared "stable" if its selection frequency ≥ `pi_thr`.
109 pub pi_thr: f64,
110 /// Base RNG seed; replicate `b` uses `seed.wrapping_add(b as u64)` for isolation.
111 pub seed: u64,
112}
113
114// ---------------------------------------------------------------------------
115// Result structs
116// ---------------------------------------------------------------------------
117
118/// Result of component-wise boosted function-on-scalar regression (REG-06-01).
119///
120/// Follows the `FosrResult` field convention; adds the boosting path diagnostics
121/// (`selected_learners`, `gcv_path`, `mstop`, `nu`).
122///
123/// **Divergence from FDboost:** fixed `mstop` / fixed `nu`; no CV-based early stopping.
124/// GCV path is tracked for post-hoc diagnostics only (see `gcv_path`).
125#[derive(Debug, Clone, PartialEq)]
126#[non_exhaustive]
127pub struct BoostFosrResult {
128 /// Intercept function F₀(t) = Ȳ(t) (pointwise mean of Y, length m_t).
129 pub intercept: Vec<f64>,
130 /// Accumulated coefficient functions β_j(t) per predictor (p × m_t).
131 ///
132 /// Row j holds the total contribution of base-learner j across all iterations
133 /// where it was selected, scaled by ν.
134 pub beta: FdMatrix,
135 /// Fitted functional values F̂(xᵢ, t) = Σ contributions (n × m_t).
136 pub fitted: FdMatrix,
137 /// Residual curves Y − F̂ (n × m_t).
138 pub residuals: FdMatrix,
139 /// Pointwise R²(t) at each response grid point (length m_t).
140 pub r_squared_t: Vec<f64>,
141 /// Integrated R² (scalar summary).
142 pub r_squared: f64,
143 /// Number of boosting iterations used.
144 pub mstop: usize,
145 /// Learning rate ν used.
146 pub nu: f64,
147 /// Index j* of the base-learner selected at each boosting iteration (length mstop).
148 pub selected_learners: Vec<usize>,
149 /// ‖residual‖_F² recorded after each iteration (length mstop).
150 ///
151 /// Should be non-increasing for L2 loss (use for path diagnostics / GCV).
152 pub gcv_path: Vec<f64>,
153}
154
155/// Result of component-wise boosted function-on-function regression (REG-06-02).
156///
157/// Functional predictors are compressed via FPCA score projection; the boosting
158/// core operates on the resulting scalar design matrices (bfpc variant).
159///
160/// **Divergence from FDboost:** uses FPC score compression (`fdata_to_pc_1d`) rather
161/// than FDboost's `bsignal` B-spline joint expansion. Simpler and dependency-free.
162#[derive(Debug, Clone, PartialEq)]
163#[non_exhaustive]
164pub struct BoostFofrResult {
165 /// Intercept function F₀(t) = Ȳ(t) (length m_y).
166 pub intercept: Vec<f64>,
167 /// Fitted response curves (n × m_y).
168 pub fitted: FdMatrix,
169 /// Residual curves (n × m_y).
170 pub residuals: FdMatrix,
171 /// Pointwise R²(t) at each response grid point (length m_y).
172 pub r_squared_t: Vec<f64>,
173 /// Overall R².
174 pub r_squared: f64,
175 /// FPCA result for each functional predictor (one per predictor).
176 pub fpca_x: Vec<crate::regression::FpcaResult>,
177 /// Accumulated FPC-space score coefficients per predictor (Vec[j] is K_j × m_y).
178 pub score_coefs: Vec<FdMatrix>,
179 /// Reconstructed coefficient surfaces β_j(s,t) per predictor (Vec[j] is m_x × m_y).
180 pub beta_surfaces: Vec<FdMatrix>,
181 /// Index j* of the base-learner selected at each boosting iteration (length mstop).
182 pub selected_learners: Vec<usize>,
183 /// ‖residual‖_F² per boosting iteration (length mstop).
184 pub gcv_path: Vec<f64>,
185 /// Number of boosting iterations used.
186 pub mstop: usize,
187 /// Learning rate ν used.
188 pub nu: f64,
189}
190
191/// Result of GAMLSS-style distributional functional regression (REG-06-03).
192///
193/// Models a Gaussian functional response Y(t) with location μ(t) and scale σ(t).
194/// Cyclic component-wise boosting alternates between boosting μ and log-σ.
195///
196/// **Divergence from gamboostLSS:** uses cyclic rather than noncyclic (non-cyclic
197/// per-iteration selection is superior for variable selection but more complex).
198/// Links: identity for μ, log for σ.
199#[derive(Debug, Clone, PartialEq)]
200#[non_exhaustive]
201pub struct GamlssResult {
202 /// Fitted location μ̂(t) per observation (n × m_t).
203 pub mu_fitted: FdMatrix,
204 /// Fitted scale σ̂(t) per observation (n × m_t); always positive.
205 pub sigma_fitted: FdMatrix,
206 /// Intercept for the μ model: F̂_μ,₀(t) = Ȳ(t) (length m_t).
207 pub mu_intercept: Vec<f64>,
208 /// Intercept for the log-σ model: 0 → exp(0) = 1 (length m_t).
209 pub sigma_intercept: Vec<f64>,
210 /// Accumulated μ coefficient functions (p × m_t).
211 pub mu_beta: FdMatrix,
212 /// Accumulated log-σ coefficient functions (p × m_t).
213 pub sigma_beta: FdMatrix,
214 /// Final Gaussian log-likelihood at convergence.
215 pub log_likelihood: f64,
216 /// Log-likelihood per cyclic iteration (length mstop).
217 pub ll_path: Vec<f64>,
218 /// Number of cyclic boosting iterations used.
219 pub mstop: usize,
220 /// Learning rate ν used.
221 pub nu: f64,
222}
223
224/// Result of Bayesian function-on-scalar regression via conjugate Gibbs (REG-06-04).
225///
226/// Posterior summaries are computed from thinned post-burn-in draws. Credible bands
227/// are pointwise (not simultaneous) quantiles over the retained draws.
228///
229/// **Divergence from refund:** uses FPCA score compression via `fdata_to_pc_1d`
230/// rather than spline basis priors. Pointwise credible bands only (no simultaneous bands).
231#[derive(Debug, Clone, PartialEq)]
232#[non_exhaustive]
233pub struct BayesianFosrResult {
234 /// Posterior mean coefficient functions β̄(t) (p × m_t).
235 pub beta_mean: FdMatrix,
236 /// Pointwise 2.5% credible band (p × m_t).
237 pub beta_lower: FdMatrix,
238 /// Pointwise 97.5% credible band (p × m_t).
239 pub beta_upper: FdMatrix,
240 /// Posterior-mean fitted values (n × m_t).
241 pub fitted: FdMatrix,
242 /// Posterior-mean residuals (n × m_t).
243 pub residuals: FdMatrix,
244 /// Posterior mean σ²(t) across the response grid (length m_t).
245 pub sigma2_mean: Vec<f64>,
246 /// Number of Gibbs iterations retained (after burn-in, before thinning).
247 pub n_iter: usize,
248 /// Burn-in iterations discarded.
249 pub burn_in: usize,
250 /// Thinning interval used.
251 pub thin: usize,
252 /// FPC components used for score compression.
253 pub ncomp: usize,
254}
255
256/// Result of FDboost-style stability selection (REG-06-05).
257///
258/// Aggregates base-learner selection frequencies over B subsamples of size ⌊n/2⌋.
259/// The PFER bound is informational: `E[V] ≤ q² / ((2·π_thr − 1)·p)`.
260#[derive(Debug, Clone, PartialEq)]
261#[non_exhaustive]
262pub struct StabilityResult {
263 /// Selection frequency π̂[j] ∈ [0, 1] for each base-learner j = 0..p.
264 pub selection_freq: Vec<f64>,
265 /// Indices j where π̂[j] ≥ pi_thr (the "stable set").
266 pub stable_set: Vec<usize>,
267 /// Threshold π used.
268 pub pi_thr: f64,
269 /// PFER upper bound: `q² / ((2·pi_thr − 1)·p)` where q = mean per-subsample selection count.
270 pub pfer_bound: f64,
271 /// Number of resamples B used.
272 pub n_resamples: usize,
273}
274
275// ---------------------------------------------------------------------------
276// Barrel re-exports
277// ---------------------------------------------------------------------------
278
279pub use self::bayesian::bayesian_fosr;
280pub use self::boost_fofr::boost_fofr;
281pub use self::boost_fosr::boost_fosr;
282pub use self::gamlss::gamlss_fosr;
283pub use self::stability::stability_selection;