antecedent_validate/
data_subset.rs1#![allow(clippy::cast_precision_loss)]
6
7use std::sync::Arc;
8
9use antecedent_core::ExecutionContext;
10use antecedent_estimate::{EstimationWorkspace, LinearAdjustmentAte};
11
12use crate::common::{
13 RefutationProblem, RefutationReport, linear_estimator_no_bootstrap, refit_effect,
14 replicate_p_value, with_row_subset,
15};
16use crate::error::ValidationError;
17
18#[derive(Clone, Debug)]
20pub struct DataSubsetRefuter {
21 pub replicates: u32,
23 pub subset_fraction: f64,
25 pub alpha: f64,
28 pub estimator: LinearAdjustmentAte,
30}
31
32impl Default for DataSubsetRefuter {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl DataSubsetRefuter {
39 #[must_use]
41 pub fn new() -> Self {
42 Self {
43 replicates: 20,
44 subset_fraction: 0.8,
45 alpha: 0.05,
46 estimator: linear_estimator_no_bootstrap(),
47 }
48 }
49
50 pub fn refute(
56 &self,
57 problem: &RefutationProblem<'_>,
58 workspace: &mut EstimationWorkspace,
59 ctx: &ExecutionContext,
60 ) -> Result<RefutationReport, ValidationError> {
61 if self.replicates < 2 {
62 return Err(ValidationError::NotApplicable {
63 message: "data subset requires replicates >= 2",
64 });
65 }
66 if !(self.subset_fraction > 0.0 && self.subset_fraction < 1.0) {
67 return Err(ValidationError::NotApplicable {
68 message: "data subset requires subset_fraction in (0, 1)",
69 });
70 }
71 let mut ates = Vec::with_capacity(self.replicates as usize);
72 for r in 0..self.replicates {
73 let data = with_row_subset(
74 problem.data,
75 self.subset_fraction,
76 ctx,
77 0xA7E0_0007_0000_u64.wrapping_add(u64::from(r)),
78 )?;
79 let est = refit_effect(
80 problem,
81 &data,
82 problem.estimand,
83 &[],
84 &self.estimator,
85 workspace,
86 ctx,
87 )?;
88 ates.push(est.ate);
89 }
90 let mean_ate = ates.iter().sum::<f64>() / f64::from(self.replicates);
91 let p_value = replicate_p_value(&ates, problem.original.ate);
92 let passed = p_value >= self.alpha;
93 Ok(RefutationReport {
94 refuter: Arc::from("data.subset"),
95 original_ate: problem.original.ate,
96 refuted_ate: mean_ate,
97 comparison: p_value,
98 informative: true,
99 passed,
100 failure_condition: if passed {
101 None
102 } else {
103 Some(Arc::from(format!(
104 "subset ATE distribution (mean {mean_ate}) is inconsistent with the \
105 original estimate (p={p_value} < alpha={}) across {}% subsets",
106 self.alpha,
107 self.subset_fraction * 100.0
108 )))
109 },
110 replicates: self.replicates,
111 })
112 }
113}