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(problem, &data, problem.estimand, &[], workspace, ctx)?;
80 ates.push(est.ate);
81 }
82 let mean_ate = ates.iter().sum::<f64>() / f64::from(self.replicates);
83 let p_value = replicate_p_value(&ates, problem.original.ate);
84 let passed = p_value >= self.alpha;
85 Ok(RefutationReport {
86 refuter: Arc::from("data.subset"),
87 original_ate: problem.original.ate,
88 refuted_ate: mean_ate,
89 comparison: p_value,
90 informative: true,
91 passed,
92 failure_condition: if passed {
93 None
94 } else {
95 Some(Arc::from(format!(
96 "subset ATE distribution (mean {mean_ate}) is inconsistent with the \
97 original estimate (p={p_value} < alpha={}) across {}% subsets",
98 self.alpha,
99 self.subset_fraction * 100.0
100 )))
101 },
102 replicates: self.replicates,
103 })
104 }
105}