Skip to main content

causal_hub/datasets/table/categorical/
incomplete.rs

1use std::{
2    borrow::Cow,
3    io::{Read, Write},
4    sync::Arc,
5};
6
7use csv::{ReaderBuilder, WriterBuilder};
8use ndarray::prelude::*;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12    datasets::{
13        CatEv, CatEvT, CatTable, CatType, CatWtdTable, Dataset, IncDataset, MissingMechanism,
14        MissingTable,
15    },
16    estimators::{BE, CPDEstimator},
17    io::CsvIO,
18    models::{CPD, CatSupport, HasLabels},
19    set, support,
20    types::{Error, Labels, Result, Set},
21};
22
23/// A struct representing an incomplete categorical dataset.
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct CatIncTable {
26    labels: Labels,
27    support: CatSupport,
28    shape: Array1<usize>,
29    values: Array2<CatType>,
30    missing: MissingTable,
31}
32
33/// Concrete iterator over incomplete categorical table evidences.
34pub struct CatIncTableEvidenceIter<'a> {
35    rows: ndarray::iter::LanesIter<'a, CatType, Ix1>,
36    support: &'a CatSupport,
37    missing: CatType,
38}
39
40impl<'a> Iterator for CatIncTableEvidenceIter<'a> {
41    type Item = Result<CatEv>;
42
43    fn next(&mut self) -> Option<Self::Item> {
44        let row = self.rows.next()?;
45
46        let evidences = row.iter().enumerate().filter_map(|(event, &state)| {
47            (state != self.missing).then_some(CatEvT::CertainPositive {
48                event,
49                state: state as usize,
50            })
51        });
52
53        Some(CatEv::new(self.support.clone(), evidences))
54    }
55}
56
57impl HasLabels for CatIncTable {
58    #[inline]
59    fn labels(&self) -> &Labels {
60        &self.labels
61    }
62}
63
64impl CatIncTable {
65    /// Creates a new categorical incomplete tabular data instance.
66    ///
67    /// # Arguments
68    ///
69    /// * `support` - The variables support.
70    /// * `values` - The values of the variables.
71    ///
72    /// # Notes
73    ///
74    /// * Labels and support will be sorted in alphabetical order.
75    ///
76    /// # Errors
77    ///
78    /// * If the number of variable support is higher than `CatType::MAX`.
79    /// * If the number of variables is different from the number of values columns.
80    /// * If the variables values are not smaller than the number of support.
81    ///
82    /// # Panics
83    ///
84    /// * If the variable labels are not unique.
85    /// * If the variable support are not unique.
86    ///
87    /// # Returns
88    ///
89    /// A new categorical incomplete tabular data instance.
90    ///
91    pub fn new(mut support: CatSupport, mut values: Array2<CatType>) -> Result<Self> {
92        // Check if the number of support is less than `CatType::MAX`.
93        support.iter().try_for_each(|(label, state)| {
94            if state.len() > CatType::MAX as usize {
95                return Err(Error::InvalidParameter(
96                    label,
97                    &format!("should have less than 256 support, found {}", state.len()),
98                ));
99            }
100            Ok(())
101        })?;
102        // Check if the number of variables is equal to the number of columns.
103        if support.len() != values.ncols() {
104            return Err(Error::IncompatibleShape(
105                &support.len().to_string(),
106                &values.ncols().to_string(),
107            ));
108        }
109        // Check if the maximum value of the values is less than the number of support.
110        let max_values = values.fold_axis(
111            Axis(0),
112            0,
113            // Find max while ignoring missing values.
114            |&a, &b| if a > b || b == Self::MISSING { a } else { b },
115        );
116        max_values.into_iter().enumerate().try_for_each(|(i, x)| {
117            if x >= support[i].len() as CatType {
118                return Err(Error::IndexOutOfBounds(x as usize));
119            }
120            Ok(())
121        })?;
122
123        // Check that the labels are sorted.
124        if !support.keys().is_sorted() {
125            // Allocate indices to sort labels.
126            let mut indices: Vec<usize> = (0..support.len()).collect();
127            // Sort the indices by labels.
128            indices.sort_by(|&i, &j| {
129                support
130                    .get_index(i)
131                    .map(|(l, _)| l)
132                    .cmp(&support.get_index(j).map(|(l, _)| l))
133            });
134            // Sort the support.
135            support.sort_keys();
136            // Allocate new values.
137            let mut new_values = values.clone();
138            // Sort the new values according to the sorted indices.
139            indices.into_iter().enumerate().for_each(|(i, j)| {
140                new_values.column_mut(i).assign(&values.column(j));
141            });
142            // Update values.
143            values = new_values;
144        }
145
146        // For each variable ...
147        values
148            .columns_mut()
149            .into_iter()
150            .zip(support.values_mut())
151            .try_for_each(|(mut col, support)| -> Result<_> {
152                // ... check if the support are sorted.
153                if !support.is_sorted() {
154                    // Clone the support.
155                    let mut new_states = support.clone();
156                    // Sort the support.
157                    new_states.sort();
158                    // Map values to sorted support.
159                    col.iter_mut().try_for_each(|value| -> Result<_> {
160                        // If the value is not missing ...
161                        if *value != Self::MISSING {
162                            // ... map it to the new state index.
163                            *value = new_states
164                                .get_index_of(&support[*value as usize])
165                                .ok_or_else(|| Error::MissingState(&support[*value as usize]))?
166                                as CatType;
167                        }
168                        Ok(())
169                    })?;
170                    // Update the support.
171                    *support = new_states;
172                }
173                Ok(())
174            })?;
175
176        // Get the labels of the variables.
177        let labels: Labels = support.keys().cloned().collect();
178        // Get the shape of the support.
179        let shape = support.values().map(Set::len).collect();
180
181        // Create the missing mask.
182        let missing_mask = values.mapv(|x| x == Self::MISSING);
183        // Initialize the missing table.
184        let missing = MissingTable::new(labels.clone(), missing_mask)?;
185
186        Ok(Self {
187            labels,
188            support,
189            shape,
190            values,
191            missing,
192        })
193    }
194
195    /// Returns the support of the variables in the categorical distribution.
196    ///
197    /// # Returns
198    ///
199    /// A reference to the vector of support.
200    ///
201    #[inline]
202    pub const fn support(&self) -> &CatSupport {
203        &self.support
204    }
205
206    /// Returns the shape of the set of support in the categorical distribution.
207    ///
208    /// # Returns
209    ///
210    /// A reference to the array of shape.
211    ///
212    #[inline]
213    pub const fn shape(&self) -> &Array1<usize> {
214        &self.shape
215    }
216}
217
218impl Dataset for CatIncTable {
219    type Values = Array2<CatType>;
220    type Support = CatSupport;
221    type Evidence = CatEv;
222    type EvidenceIter<'a> = CatIncTableEvidenceIter<'a>;
223
224    #[inline]
225    fn values(&self) -> &Self::Values {
226        &self.values
227    }
228
229    #[inline]
230    fn support(&self) -> Cow<'_, Self::Support> {
231        Cow::Borrowed(&self.support)
232    }
233
234    fn evidence_iter(&self) -> Self::EvidenceIter<'_> {
235        CatIncTableEvidenceIter {
236            rows: self.values.rows().into_iter(),
237            support: &self.support,
238            missing: Self::MISSING,
239        }
240    }
241
242    #[inline]
243    fn sample_size(&self) -> f64 {
244        self.values.nrows() as f64
245    }
246
247    fn select(&self, x: &Set<usize>) -> Result<Self> {
248        // Check that the indices are valid.
249        x.iter().try_for_each(|&i| {
250            if i >= self.values.ncols() {
251                return Err(Error::IndexOutOfBounds(i));
252            }
253            Ok(())
254        })?;
255
256        // Select the support.
257        let support: CatSupport = x
258            .iter()
259            .map(|&i| {
260                self.support
261                    .get_index(i)
262                    .map(|(label, support)| (label.clone(), support.clone()))
263                    .ok_or_else(|| Error::IndexOutOfBounds(i))
264            })
265            .collect::<Result<_>>()?;
266
267        // Select the values.
268        let mut new_values = Array2::zeros((self.values.nrows(), x.len()));
269        // Copy the selected columns.
270        x.iter().enumerate().for_each(|(j, &i)| {
271            new_values.column_mut(j).assign(&self.values.column(i));
272        });
273        // Update the values.
274        let values = new_values;
275
276        // Return the new dataset.
277        Self::new(support, values)
278    }
279}
280
281impl IncDataset for CatIncTable {
282    type Missing = CatType;
283    const MISSING: Self::Missing = CatType::MAX;
284
285    type Complete = CatTable;
286    type Weighted = CatWtdTable;
287
288    #[inline]
289    fn missing(&self) -> &MissingTable {
290        &self.missing
291    }
292
293    fn ipw_weights(
294        &self,
295        d_u: &Self::Complete,
296        u: &Set<usize>,
297        pr: &MissingMechanism,
298    ) -> Result<Array1<f64>> {
299        // Get (`R_i`, `Pi_R_i`) associated to `U_i`.
300        let pr_iter = u.iter().filter_map(|&ri| pr.get(&ri).map(|pri| (ri, pri)));
301        // Filter out `R_i` with no parents.
302        let pr_iter = pr_iter.filter(|(_, pri)| !pri.is_empty());
303
304        // Define function to compute the weights associated to each `R_i`.
305        let beta_i = |d_u: &Self::Complete, ri: usize, pri: &Set<usize>| -> Result<Array1<f64>> {
306            /* Compute P(Pi_R_i | R_Pi_R_i = 0) and P(Pi_R_i | R_i = 0, R_Pi_R_i = 0) */
307
308            // Apply pairwise deletion.
309            let d_pri_rpri = self.pw_deletion(pri)?;
310            let d_pri_ri_rpri = self.pw_deletion(&(&set![ri] | pri))?;
311            // Map the indices w.r.t. the new dataset.
312            let x_pri_rpri = d_pri_rpri.indices_from(pri, self.labels())?;
313            let x_pri_ri_rpri = d_pri_ri_rpri.indices_from(pri, self.labels())?;
314            // Compute the distribution.
315            let p_pri_rpri = BE::new(&d_pri_rpri).fit(&x_pri_rpri, &set![])?;
316            let p_pri_ri_rpri = BE::new(&d_pri_ri_rpri).fit(&x_pri_ri_rpri, &set![])?;
317
318            // Map indices of pri w.r.t d_u.
319            let x_pri_u = d_u.indices_from(pri, self.labels())?;
320
321            // Allocate the `R_i`-specific weights.
322            let mut b_pri_rpri = Array::zeros(d_u.values().nrows());
323            let mut b_pri_ri_rpri = b_pri_rpri.clone();
324            // Fill the `R_i`-specific weights.
325            for (d_u_j, (b_pri_rpri_j, b_pri_ri_rpri_j)) in d_u
326                .values()
327                .rows()
328                .into_iter()
329                .zip(b_pri_rpri.iter_mut().zip(b_pri_ri_rpri.iter_mut()))
330            {
331                // Get the parents values for the j-th rows.
332                let pri_j = x_pri_u.iter().map(|&j| d_u_j[j]).collect();
333                // Get the parents weights associated to each row.
334                *b_pri_rpri_j = p_pri_rpri.pf(&pri_j, &array![])?;
335                *b_pri_ri_rpri_j = p_pri_ri_rpri.pf(&pri_j, &array![])?;
336            }
337            // Compute the `R_i`-specific weights.
338            Ok(b_pri_rpri / b_pri_ri_rpri)
339        };
340
341        // Compute the weights associated to each `R_i`.
342        let mut beta = Array::ones(d_u.values().nrows());
343        for (ri, pri) in pr_iter {
344            let beta_i = beta_i(d_u, ri, pri)?;
345            beta *= &beta_i;
346        }
347
348        // Rescale the weights.
349        if beta.sum() > 0. {
350            beta *= (beta.len() as f64) / beta.sum();
351        }
352
353        Ok(beta)
354    }
355
356    fn lw_deletion(&self) -> Result<Self::Complete> {
357        // Allocate new values.
358        let mut new_values = Array::zeros((
359            self.missing.complete_rows_count(), //
360            self.values.ncols(),
361        ));
362
363        // Get complete rows.
364        let rows = self
365            .values
366            .rows()
367            .into_iter()
368            .zip(self.missing.missing_mask_by_rows())
369            // Filter for complete rows only.
370            .filter_map(|(row, &is_complete)| if !is_complete { Some(row) } else { None });
371
372        // Fill new values with complete rows only.
373        rows.zip(new_values.rows_mut())
374            .for_each(|(row, mut new_row)| new_row.assign(&row));
375
376        // Return new complete dataset.
377        Self::Complete::new(self.support.clone(), new_values)
378    }
379
380    fn pw_deletion(&self, x: &Set<usize>) -> Result<Self::Complete> {
381        // If no columns are specified, return an empty dataset.
382        if x.is_empty() {
383            let stats = support![];
384            let v = Array::default((0, 0));
385            return Self::Complete::new(stats, v);
386        }
387
388        // Check that the indices are valid.
389        x.iter().try_for_each(|&i| {
390            if i >= self.values.ncols() {
391                return Err(Error::IndexOutOfBounds(i));
392            }
393            Ok(())
394        })?;
395
396        // Clone the indices.
397        let mut cols = x.clone();
398        // Sort the indices.
399        cols.sort();
400
401        // Get the indices of complete rows for the specified columns.
402        let rows: Vec<_> = self
403            .missing
404            .missing_mask()
405            .rows()
406            .into_iter()
407            .enumerate()
408            .filter_map(|(i, row)| {
409                // Check if all specified columns are not missing.
410                if !cols.iter().any(|&j| row[j]) {
411                    Some(i)
412                } else {
413                    None
414                }
415            })
416            .collect();
417
418        // Collect the values for the specified rows and columns.
419        let new_values = Array::from_shape_fn(
420            (rows.len(), cols.len()), //
421            |(i, j)| self.values[[rows[i], cols[j]]],
422        );
423
424        // Select the support for the specified columns.
425        let new_states = cols
426            .iter()
427            .map(|&j| {
428                self.support
429                    .get_index(j)
430                    .map(|(label, state)| (label.clone(), state.clone()))
431                    .ok_or_else(|| Error::IndexOutOfBounds(j))
432            })
433            .collect::<Result<_>>()?;
434
435        // Return new complete dataset.
436        Self::Complete::new(new_states, new_values)
437    }
438
439    fn ipw_deletion(&self, x: &Set<usize>, pr: &MissingMechanism) -> Result<Self::Weighted> {
440        // If no columns are specified, return an empty dataset.
441        if x.is_empty() {
442            let stats = support![];
443            let v = Array::default((0, 0));
444            let w = Array::default(0);
445            return Self::Weighted::new(Self::Complete::new(stats, v)?, w);
446        }
447
448        // Check that the indices are valid.
449        x.iter().try_for_each(|&i| {
450            if i >= self.values.ncols() {
451                return Err(Error::IndexOutOfBounds(i));
452            }
453            Ok(())
454        })?;
455        // Check that the missing mechanism indices are valid.
456        pr.keys().try_for_each(|&i| {
457            if i >= self.values.ncols() {
458                return Err(Error::IndexOutOfBounds(i));
459            }
460            Ok(())
461        })?;
462        // Check that the missing mechanism is sorted.
463        if !pr.keys().is_sorted() {
464            return Err(Error::InvalidParameter(
465                "missing_mechanism",
466                "keys must be sorted.",
467            ));
468        }
469        if !pr.values().all(|pri| pri.iter().is_sorted()) {
470            return Err(Error::InvalidParameter(
471                "missing_mechanism",
472                "values must be sorted.",
473            ));
474        }
475
476        // Compute U recursively from X and Pi_R following the IPW algorithm.
477        let mut u = x.clone();
478        let mut pru: Set<_> = x
479            .iter()
480            .flat_map(|&x| pr.get(&x).cloned())
481            .flatten()
482            .collect();
483        // Compute the transitive closure of the parents.
484        while !pru.is_subset(&u) {
485            u.extend(pru.drain(..));
486            pru.extend(u.iter().flat_map(|&u| pr.get(&u).cloned()).flatten());
487        }
488        // Sort U.
489        u.sort();
490
491        // Apply pairwise deletion.
492        let d_u = self.pw_deletion(&u)?;
493        // Compute the weights w.r.t. pairwise deleted dataset.
494        let b_u = self.ipw_weights(&d_u, &u, pr)?;
495
496        // Map the indices to the restricted dataset.
497        let x = d_u.indices_from(x, self.labels())?;
498        // Since U is a superset of X, restrict U to X.
499        let d_x = d_u.select(&x)?;
500
501        // Return new weighted dataset.
502        Self::Weighted::new(d_x, b_u)
503    }
504
505    fn aipw_deletion(&self, x: &Set<usize>, pr: &MissingMechanism) -> Result<Self::Weighted> {
506        // If no columns are specified, return an empty dataset.
507        if x.is_empty() {
508            let stats = support![];
509            let v = Array::default((0, 0));
510            let w = Array::default(0);
511            return Self::Weighted::new(Self::Complete::new(stats, v)?, w);
512        }
513
514        // Check that the indices are valid.
515        x.iter().try_for_each(|&i| {
516            if i >= self.values.ncols() {
517                return Err(Error::IndexOutOfBounds(i));
518            }
519            Ok(())
520        })?;
521        // Check that the missing mechanism indices are valid.
522        pr.keys().try_for_each(|&i| {
523            if i >= self.values.ncols() {
524                return Err(Error::IndexOutOfBounds(i));
525            }
526            Ok(())
527        })?;
528        // Check that the missing mechanism is sorted.
529        if !pr.keys().is_sorted() {
530            return Err(Error::InvalidParameter(
531                "missing_mechanism",
532                "keys must be sorted.",
533            ));
534        }
535        if !pr.values().all(|pri| pri.iter().is_sorted()) {
536            return Err(Error::InvalidParameter(
537                "missing_mechanism",
538                "values must be sorted.",
539            ));
540        }
541
542        // Compute W recursively from X and Pi_R following the IPW algorithm.
543        let mut w = x.clone();
544        let prw: Set<_> = x
545            .iter()
546            .flat_map(|x| pr.get(x).cloned())
547            .flatten()
548            .collect();
549        // Sort W.
550        w.sort();
551
552        // Get the set of partially observed variables.
553        let v_m = self.missing().partially_observed();
554        // Check if the intersection of Pi_R_W and V_M is empty.
555        if (&(&prw - &w) & v_m).is_empty() {
556            return self.ipw_deletion(x, pr); // ... IPW.
557        };
558
559        // Otherwise, apply pairwise deletion w.r.t. X.
560        let d_x = self.pw_deletion(x)?;
561        let b_x = Array::ones(d_x.values().nrows()); // ... aIPW.
562        // Return new weighted dataset.
563        Self::Weighted::new(d_x, b_x)
564    }
565}
566
567impl CsvIO for CatIncTable {
568    fn from_csv_reader<R: Read>(reader: R) -> Result<Self> {
569        // Create a CSV reader from the string.
570        let mut reader = ReaderBuilder::new().has_headers(true).from_reader(reader);
571
572        // Check if the reader has headers.
573        if !reader.has_headers() {
574            return Err(Error::MissingHeader());
575        }
576
577        // Read the headers.
578        let labels: Labels = reader
579            .headers()?
580            .into_iter()
581            .map(|x| x.to_owned())
582            .collect();
583
584        // Get the support of the variables.
585        let mut support: CatSupport = labels
586            .iter()
587            .map(|x| (x.clone(), Default::default()))
588            .collect();
589
590        // Read the records.
591        let values: Vec<CatType> =
592            reader
593                .into_records()
594                .try_fold(Vec::new(), |mut values, row| -> Result<_> {
595                    // Get the record row.
596                    let row = row.map_err(|evidence| Error::Csv(Arc::new(evidence)))?;
597                    // Get the record values and convert to indices.
598                    values.extend(
599                        row.into_iter()
600                            .zip(support.values_mut())
601                            .map(|(x, support)| {
602                                // Check if the value is missing.
603                                if x.is_empty() {
604                                    Self::MISSING
605                                } else {
606                                    // Insert the value into the support, if not present.
607                                    let (x, _) = support.insert_full(x.to_owned());
608                                    // Cast the value.
609                                    x as CatType
610                                }
611                            }),
612                    );
613
614                    Ok(values)
615                })?;
616
617        // Get the number of rows and columns.
618        let ncols = labels.len();
619        let nrows = values.len() / ncols;
620        // Reshape the values to the correct shape.
621        let values = Array1::from_vec(values).into_shape_with_order((nrows, ncols))?;
622
623        // Construct the dataset.
624        Self::new(support, values)
625    }
626
627    fn to_csv_writer<W: Write>(&self, writer: W) -> Result<()> {
628        // Create the CSV writer.
629        let mut writer = WriterBuilder::new().has_headers(true).from_writer(writer);
630
631        // Write the headers.
632        writer.write_record(self.labels.iter())?;
633
634        // Create an empty string for missing values.
635        let missing = String::new();
636
637        // Write the records.
638        for row in self.values.rows() {
639            // Zip the row with the support.
640            let record = row.iter().zip(self.support().values());
641            // Map the row values to support.
642            let record = record.map(|(&x, support)| {
643                // Check if the value is missing.
644                if x == Self::MISSING {
645                    return &missing;
646                }
647                // Return the state label.
648                &support[x as usize]
649            });
650            // Write the record.
651            writer.write_record(record)?;
652        }
653
654        Ok(())
655    }
656}