use std::sync::Arc;
use crate::common::{RefutationProblem, RefutationReport, complete_case_rows, masked_sample_sd};
use crate::error::ValidationError;
pub const DEFAULT_EVALUE_THRESHOLD: f64 = 2.0;
#[derive(Clone, Debug)]
pub struct EValue {
pub threshold: f64,
}
impl Default for EValue {
fn default() -> Self {
Self::new()
}
}
impl EValue {
#[must_use]
pub fn new() -> Self {
Self { threshold: DEFAULT_EVALUE_THRESHOLD }
}
#[must_use]
pub fn with_threshold(threshold: f64) -> Self {
Self { threshold }
}
pub fn refute(
&self,
problem: &RefutationProblem<'_>,
) -> Result<RefutationReport, ValidationError> {
let mut ids = vec![problem.treatment(), problem.outcome()];
ids.extend_from_slice(&problem.estimand.adjustment_set);
let (mask, _valid) = complete_case_rows(problem.data, &ids)?;
let sd_y = masked_sample_sd(problem.data, problem.outcome(), &mask)?;
if !(sd_y.is_finite() && sd_y > 0.0) {
return Err(ValidationError::NotApplicable {
message: "e-value requires a finite, positive outcome standard deviation",
});
}
let d = problem.original.ate / sd_y;
let rr = (0.91 * d).exp();
let e_value = e_value_from_risk_ratio(rr);
let passed = e_value >= self.threshold;
Ok(RefutationReport {
refuter: Arc::from("sensitivity.evalue"),
original_ate: problem.original.ate,
refuted_ate: problem.original.ate,
comparison: e_value,
informative: true,
passed,
failure_condition: if passed {
None
} else {
Some(Arc::from(format!("e-value {e_value} below threshold {}", self.threshold)))
},
replicates: 0,
})
}
}
fn e_value_from_risk_ratio(rr: f64) -> f64 {
let rr = if rr >= 1.0 { rr } else { 1.0 / rr };
rr + (rr * (rr - 1.0)).sqrt()
}