Skip to main content

causal_hub/random/models/graphs/
missing.rs

1use rand::{
2    Rng,
3    seq::{IndexedRandom, IteratorRandom, SliceRandom},
4};
5
6use crate::{
7    datasets::{MissingMechanism, MissingType},
8    inference::VStructures,
9    models::{DiGraph, Graph, HasLabels},
10    random::Random,
11    set,
12    types::{Error, Map, Result, Set},
13};
14
15/// A struct representing a random missingness mechanism generator.
16pub struct RngMissingMechanism<'a, R> {
17    rng: &'a mut R,
18    graph: &'a DiGraph,
19    missing: MissingType,
20    probability: f64,
21}
22
23impl<'a, R> RngMissingMechanism<'a, R> {
24    /// Creates a new `RngMissingMechanism` instance.
25    ///
26    /// # Arguments
27    ///
28    /// * `rng` - A mutable reference to a random number generator.
29    /// * `graph` - The graph on which to generate the missingness mechanism.
30    /// * `missing` - The type of missingness mechanism to generate.
31    /// * `p` - The ratio of missing variables.
32    ///
33    /// # Returns
34    ///
35    /// A new `RngMissingMechanism` instance.
36    ///
37    pub fn new(
38        rng: &'a mut R,
39        graph: &'a DiGraph,
40        missing: MissingType,
41        probability: f64,
42    ) -> Result<Self> {
43        // Check if the ratio of missing variables is in [0, 1].
44        if !(0.0..=1.0).contains(&probability) {
45            return Err(Error::InvalidParameter("p", "must be in [0, 1]"));
46        }
47
48        Ok(Self {
49            rng,
50            graph,
51            missing,
52            probability,
53        })
54    }
55}
56
57impl<R: Rng> RngMissingMechanism<'_, R> {
58    /// Generates a random missingness mechanism of type MCAR.
59    ///
60    /// # Returns
61    ///
62    /// A map where keys are missing variable indices and values are empty sets (no causes).
63    ///
64    pub fn random_mcar(&mut self) -> Result<MissingMechanism> {
65        // Get the number of vertices.
66        let v = self.graph.vertices();
67        // Calculate the total number of missing variables.
68        let n = (v.len() as f64 * self.probability).round() as usize;
69        // Randomly select n variables to be missing.
70        let model = v.into_iter().sample(self.rng, n);
71        // Create the missingness mechanism with empty cause sets.
72        let pr = MissingMechanism::new(
73            self.graph.labels().clone(),
74            model.into_iter().map(|x| (x, set![])).collect(),
75        )?;
76
77        Ok(pr)
78    }
79
80    /// Generates a random missingness mechanism of type MAR.
81    ///
82    /// # Returns
83    ///
84    /// A map where keys are missing variable indices and values are sets of observed variable indices causing the missingness.
85    ///
86    pub fn random_mar(&mut self) -> Result<MissingMechanism> {
87        // Get the number of vertices.
88        let v = self.graph.vertices();
89        // Calculate the total number of missing variables.
90        let n = (v.len() as f64 * self.probability).round() as usize;
91        // Initialize the cause dictionary.
92        let mut pr = MissingMechanism::new(self.graph.labels().clone(), Map::default())?;
93
94        // Precompute v-structures.
95        let v_structs = self.graph.v_structures()?;
96
97        let mut model = Set::default();
98        let mut o = Set::default();
99
100        // 1. Prefer v-structures
101        for (x, z, y) in v_structs {
102            if model.len() >= n {
103                break;
104            }
105
106            for &u in &[x, y] {
107                if !model.contains(&u) && !o.contains(&u) {
108                    model.insert(u);
109                    o.insert(z);
110                    pr.insert(u, set![z]);
111
112                    if model.len() >= n {
113                        break;
114                    }
115                }
116            }
117        }
118
119        // 2. Fill remaining missing variables
120        if model.len() < n {
121            let mut remaining: Vec<_> = v
122                .iter()
123                .copied()
124                .filter(|&u| !model.contains(&u) && !o.contains(&u))
125                .collect();
126            remaining.shuffle(self.rng);
127            let extra_count = (n - model.len()).min(remaining.len());
128            for &u in &remaining[..extra_count] {
129                model.insert(u);
130            }
131            o = v.iter().copied().filter(|u| !model.contains(u)).collect();
132        }
133
134        // 3. Assign MAR causes
135        let vars_obs_vec: Vec<_> = o.iter().copied().collect();
136        for &x in &model {
137            if pr.contains_key(&x) {
138                continue;
139            }
140
141            let predecessors = self.graph.parents(&set![x])?;
142            let successors = self.graph.children(&set![x])?;
143            let neighbors = predecessors.union(&successors).copied().collect::<Set<_>>();
144            let candidates: Vec<_> = neighbors.intersection(&o).copied().collect();
145
146            if let Some(&z) = candidates.choose(self.rng) {
147                pr.insert(x, set![z]);
148            } else if let Some(&z) = vars_obs_vec.choose(self.rng) {
149                pr.insert(x, set![z]);
150            }
151        }
152
153        Ok(pr)
154    }
155
156    /// Generates a random missingness mechanism of type MNAR.
157    ///
158    /// # Returns
159    ///
160    /// A map where keys are missing variable indices and values are sets of observed variable indices causing the missingness.
161    ///
162    pub fn random_mnar(&mut self) -> Result<MissingMechanism> {
163        // Get the number of vertices.
164        let v = self.graph.vertices();
165        // Calculate the total number of missing variables.
166        let n = (v.len() as f64 * self.probability).round() as usize;
167
168        // Initialize the cause dictionary.
169        let mut pr = MissingMechanism::new(self.graph.labels().clone(), Map::default())?;
170
171        // Precompute v-structures.
172        let v_structs = self.graph.v_structures()?;
173
174        let p_mnar = (n as f64 / 2.0).round() as usize;
175
176        let mut vars_miss_mnar = Set::default();
177        let mut model = Set::default();
178
179        // 1. Assign MNAR variables via v-structures
180        for (x, z, y) in v_structs {
181            if vars_miss_mnar.len() >= p_mnar {
182                break;
183            }
184
185            for &u in &[x, y] {
186                if !model.contains(&u) {
187                    vars_miss_mnar.insert(u);
188                    model.insert(u);
189                    model.insert(z);
190                    pr.insert(u, set![z]);
191
192                    if vars_miss_mnar.len() >= p_mnar {
193                        break;
194                    }
195                }
196            }
197        }
198
199        // 2. MAR part
200        let vars_miss_mar: Vec<_> = model.difference(&vars_miss_mnar).copied().collect();
201        let o: Set<_> = v.iter().copied().filter(|u| !model.contains(u)).collect();
202        let vars_obs_vec: Vec<_> = o.iter().copied().collect();
203
204        for &x in &vars_miss_mar {
205            let predecessors = self.graph.parents(&set![x])?;
206            let successors = self.graph.children(&set![x])?;
207            let neighbors = predecessors.union(&successors).copied().collect::<Set<_>>();
208            let candidates: Vec<_> = neighbors.intersection(&o).copied().collect();
209
210            if let Some(&z) = candidates.choose(self.rng) {
211                pr.insert(x, set![z]);
212            } else if let Some(&z) = vars_obs_vec.choose(self.rng) {
213                pr.insert(x, set![z]);
214            }
215        }
216
217        // 3. Fill remaining missing variables if needed
218        while model.len() < n {
219            let remaining: Vec<_> = v.iter().copied().filter(|u| !model.contains(u)).collect();
220            if remaining.is_empty() {
221                break;
222            }
223
224            if let Some(&x) = remaining.choose(self.rng) {
225                // Z = random.choice(list(set(V) - m))
226                // Note: remaining still contains x at this point in Python logic if it's the same set.
227                if let Some(&z) = remaining.choose(self.rng) {
228                    model.insert(x);
229                    pr.insert(x, set![z]);
230                }
231            }
232        }
233
234        Ok(pr)
235    }
236}
237
238impl<R: Rng> Random for RngMissingMechanism<'_, R> {
239    type Output = Result<MissingMechanism>;
240
241    fn random(&mut self) -> Self::Output {
242        // Generate the missingness mechanism based on the specified type.
243        match self.missing {
244            MissingType::MCAR => self.random_mcar(),
245            MissingType::MAR => self.random_mar(),
246            MissingType::MNAR => self.random_mnar(),
247        }
248    }
249}