use crate::error::{Result, StatError};
use crate::resampling::bootstrap::StationaryBootstrap;
pub fn validate_model_data(
benchmark_len: usize,
model_data: &[Vec<f64>],
data_name: &str,
) -> Result<()> {
if benchmark_len == 0 {
return Err(StatError::EmptyData);
}
if model_data.is_empty() {
return Err(StatError::InvalidParameter(format!(
"At least one {} required",
data_name
)));
}
for (i, data) in model_data.iter().enumerate() {
if data.len() != benchmark_len {
return Err(StatError::InvalidParameter(format!(
"Model {} has {} observations, expected {}",
i,
data.len(),
benchmark_len
)));
}
}
Ok(())
}
pub fn compute_means(differentials: &[Vec<f64>]) -> Vec<f64> {
let t = differentials[0].len() as f64;
differentials
.iter()
.map(|d| d.iter().sum::<f64>() / t)
.collect()
}
pub fn compute_variances(differentials: &[Vec<f64>], means: &[f64]) -> Vec<f64> {
let t = differentials[0].len() as f64;
differentials
.iter()
.zip(means.iter())
.map(|(d, &mean)| {
let var: f64 = d.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (t - 1.0);
var / t })
.collect()
}
pub fn compute_standardized(means: &[f64], variances: &[f64], t: usize) -> Vec<f64> {
let t_f = t as f64;
means
.iter()
.zip(variances.iter())
.map(|(&mean, &var)| {
if var > 1e-14 {
mean * t_f.sqrt() / var.sqrt()
} else {
mean * t_f.sqrt() * 1e6
}
})
.collect()
}
pub fn find_best_model(standardized: &[f64]) -> Option<usize> {
standardized
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(i, _)| i)
}
pub fn compute_spa_pvalues(
differentials: &[Vec<f64>],
d_bar: &[f64],
observed: f64,
n_bootstrap: usize,
block_length: f64,
seed: Option<u64>,
) -> (f64, f64) {
let t = differentials[0].len();
let t_f = t as f64;
let mut bootstrap = StationaryBootstrap::new(block_length, seed);
let mut count_consistent = 0usize;
let mut count_upper = 0usize;
for _ in 0..n_bootstrap {
let mut boot_max_consistent = f64::NEG_INFINITY;
let mut boot_max_upper = f64::NEG_INFINITY;
for (i, di) in differentials.iter().enumerate() {
let boot_sample = bootstrap.sample(di, t);
let boot_mean: f64 = boot_sample.iter().sum::<f64>() / t_f;
let boot_var: f64 = boot_sample
.iter()
.map(|x| (x - boot_mean).powi(2))
.sum::<f64>()
/ (t_f - 1.0)
/ t_f;
let centered_mean = boot_mean - d_bar[i];
let boot_stat_consistent = standardize_single(centered_mean, boot_var, t_f);
let g_d_bar = d_bar[i].max(0.0);
let upper_centered = boot_mean - g_d_bar;
let boot_stat_upper = standardize_single(upper_centered, boot_var, t_f);
boot_max_consistent = boot_max_consistent.max(boot_stat_consistent);
boot_max_upper = boot_max_upper.max(boot_stat_upper);
}
if boot_max_consistent >= observed {
count_consistent += 1;
}
if boot_max_upper >= observed {
count_upper += 1;
}
}
let p_consistent = (count_consistent as f64 + 1.0) / (n_bootstrap as f64 + 1.0);
let p_upper = (count_upper as f64 + 1.0) / (n_bootstrap as f64 + 1.0);
(p_consistent, p_upper)
}
#[inline]
fn standardize_single(mean: f64, var: f64, t_f: f64) -> f64 {
if var > 1e-14 {
mean * t_f.sqrt() / var.sqrt()
} else {
mean * t_f.sqrt() * 1e6
}
}