pub fn bootstrap_stats(estimates: &[f64], _point_estimate: f64) -> (f64, f64, (f64, f64)) {
if estimates.is_empty() {
return (f64::NAN, f64::NAN, (f64::NAN, f64::NAN));
}
let n = estimates.len() as f64;
let mean: f64 = estimates.iter().sum::<f64>() / n;
let std_err = (estimates
.iter()
.map(|&val| (val - mean).powi(2))
.sum::<f64>()
/ (n - 1.0))
.sqrt();
let prop_positive = estimates.iter().filter(|&&val| val >= 0.0).count() as f64 / n;
let prop_negative = estimates.iter().filter(|&&val| val <= 0.0).count() as f64 / n;
let p_value = (2.0 * prop_positive.min(prop_negative)).min(1.0);
let mut sorted_estimates = estimates.to_vec();
sorted_estimates.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
let lower_idx = (0.025 * n).floor() as usize;
let upper_idx = ((0.975 * n).floor() as usize).min(estimates.len().saturating_sub(1));
let ci_lower = sorted_estimates.get(lower_idx).copied().unwrap_or(f64::NAN);
let ci_upper = sorted_estimates.get(upper_idx).copied().unwrap_or(f64::NAN);
(std_err, p_value, (ci_lower, ci_upper))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bootstrap_stats_p_value() {
let estimates = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let (_, p_val, _) = bootstrap_stats(&estimates, 3.0);
assert_eq!(p_val, 0.0);
let estimates = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
let (_, p_val, _) = bootstrap_stats(&estimates, 0.0);
assert!((p_val - 1.0).abs() < 1e-9);
let estimates = vec![-1.0, 1.0, 2.0, 3.0, 4.0];
let (_, p_val, _) = bootstrap_stats(&estimates, 2.0);
assert!((p_val - 0.4).abs() < 1e-9);
}
}