Skip to main content

causal_hub/datasets/table/gaussian/
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        Dataset, GaussEv, GaussEvT, GaussTable, GaussType, GaussWtdTable, IncDataset,
14        MissingMechanism, MissingTable,
15    },
16    estimators::{BE, CPDEstimator},
17    io::CsvIO,
18    labels,
19    models::{CPD, GaussSupport, HasLabels},
20    set,
21    types::{Error, Labels, Result, Set},
22};
23
24/// A struct representing an incomplete gaussian dataset.
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub struct GaussIncTable {
27    labels: Labels,
28    values: Array2<GaussType>,
29    missing: MissingTable,
30}
31
32/// Concrete iterator over incomplete Gaussian table evidences.
33pub struct GaussIncTableEvidenceIter<'a> {
34    rows: ndarray::iter::LanesIter<'a, GaussType, Ix1>,
35    labels: &'a Labels,
36}
37
38impl<'a> Iterator for GaussIncTableEvidenceIter<'a> {
39    type Item = Result<GaussEv>;
40
41    fn next(&mut self) -> Option<Self::Item> {
42        let row = self.rows.next()?;
43
44        let evidences = row.iter().enumerate().filter_map(|(event, &value)| {
45            (!value.is_nan()).then_some(GaussEvT::CertainPositive { event, value })
46        });
47
48        Some(GaussEv::new(self.labels.clone(), evidences))
49    }
50}
51
52impl HasLabels for GaussIncTable {
53    #[inline]
54    fn labels(&self) -> &Labels {
55        &self.labels
56    }
57}
58
59impl GaussIncTable {
60    /// Creates a new gaussian incomplete tabular data instance.
61    pub fn new(mut labels: Labels, mut values: Array2<GaussType>) -> Result<Self> {
62        // Check if the number of variables is equal to the number of columns.
63        if labels.len() != values.ncols() {
64            return Err(Error::IncompatibleShape(
65                &labels.len().to_string(),
66                &values.ncols().to_string(),
67            ));
68        }
69
70        // Check that the labels are sorted.
71        if !labels.is_sorted() {
72            // Allocate indices to sort labels.
73            let mut indices: Vec<usize> = (0..labels.len()).collect();
74            // Sort the indices by labels.
75            indices.sort_by_key(|&i| &labels[i]);
76            // Sort the labels.
77            labels.sort();
78            // Allocate new values.
79            let mut new_values = values.clone();
80            // Sort the new values according to the sorted indices.
81            indices.into_iter().enumerate().for_each(|(i, j)| {
82                new_values.column_mut(i).assign(&values.column(j));
83            });
84            // Update values.
85            values = new_values;
86        }
87
88        // Create the missing mask.
89        let missing_mask = values.mapv(|x| x.is_nan());
90        // Initialize the missing table.
91        let missing = MissingTable::new(labels.clone(), missing_mask)?;
92
93        Ok(Self {
94            labels,
95            values,
96            missing,
97        })
98    }
99}
100
101impl Dataset for GaussIncTable {
102    type Values = Array2<GaussType>;
103    type Support = GaussSupport;
104    type Evidence = GaussEv;
105    type EvidenceIter<'a> = GaussIncTableEvidenceIter<'a>;
106
107    #[inline]
108    fn values(&self) -> &Self::Values {
109        &self.values
110    }
111
112    fn support(&self) -> Cow<'_, Self::Support> {
113        Cow::Owned(
114            self.labels
115                .iter()
116                .map(|l| (l.clone(), (f64::NEG_INFINITY, f64::INFINITY)))
117                .collect(),
118        )
119    }
120
121    fn evidence_iter(&self) -> Self::EvidenceIter<'_> {
122        GaussIncTableEvidenceIter {
123            rows: self.values.rows().into_iter(),
124            labels: &self.labels,
125        }
126    }
127
128    #[inline]
129    fn sample_size(&self) -> f64 {
130        self.values.nrows() as f64
131    }
132
133    fn select(&self, x: &Set<usize>) -> Result<Self> {
134        // Check that the indices are valid.
135        x.iter().try_for_each(|&i| {
136            if i >= self.values.ncols() {
137                return Err(Error::IndexOutOfBounds(i));
138            }
139            Ok(())
140        })?;
141
142        // Select the labels.
143        let labels: Labels = x
144            .iter()
145            .map(|&i| {
146                self.labels
147                    .get_index(i)
148                    .cloned()
149                    .ok_or_else(|| Error::IndexOutOfBounds(i))
150            })
151            .collect::<Result<_>>()?;
152
153        // Select the values.
154        let mut new_values = Array2::zeros((self.values.nrows(), x.len()));
155        // Copy the selected columns.
156        x.iter().enumerate().for_each(|(j, &i)| {
157            new_values.column_mut(j).assign(&self.values.column(i));
158        });
159        // Update the values.
160        let values = new_values;
161
162        // Return the new dataset.
163        Self::new(labels, values)
164    }
165}
166
167impl IncDataset for GaussIncTable {
168    type Missing = GaussType;
169    const MISSING: Self::Missing = GaussType::NAN;
170
171    type Complete = GaussTable;
172    type Weighted = GaussWtdTable;
173
174    #[inline]
175    fn missing(&self) -> &MissingTable {
176        &self.missing
177    }
178
179    fn ipw_weights(
180        &self,
181        d_u: &Self::Complete,
182        u: &Set<usize>,
183        pr: &MissingMechanism,
184    ) -> Result<Array1<f64>> {
185        // Get (`R_i`, `Pi_R_i`) associated to `U_i`.
186        let pr_iter = u.iter().filter_map(|&ri| pr.get(&ri).map(|pri| (ri, pri)));
187        // Filter out `R_i` with no parents.
188        let pr_iter = pr_iter.filter(|(_, pri)| !pri.is_empty());
189
190        // Define function to compute the weights associated to each `R_i`.
191        let beta_i = |d_u: &Self::Complete, ri: usize, pri: &Set<usize>| -> Result<Array1<f64>> {
192            /* Compute P(Pi_R_i | R_Pi_R_i = 0) and P(Pi_R_i | R_i = 0, R_Pi_R_i = 0) */
193
194            // Apply pairwise deletion.
195            let d_pri_rpri = self.pw_deletion(pri)?;
196            let d_pri_ri_rpri = self.pw_deletion(&(&set![ri] | pri))?;
197            // Map the indices w.r.t. the new dataset.
198            let x_pri_rpri = d_pri_rpri.indices_from(pri, self.labels())?;
199            let x_pri_ri_rpri = d_pri_ri_rpri.indices_from(pri, self.labels())?;
200            // Compute the distribution.
201            let p_pri_rpri = BE::new(&d_pri_rpri).fit(&x_pri_rpri, &set![])?;
202            let p_pri_ri_rpri = BE::new(&d_pri_ri_rpri).fit(&x_pri_ri_rpri, &set![])?;
203
204            // Map indices of pri w.r.t d_u.
205            let x_pri_u = d_u.indices_from(pri, self.labels())?;
206
207            // Allocate the `R_i`-specific weights.
208            let mut b_pri_rpri = Array::zeros(d_u.values().nrows());
209            let mut b_pri_ri_rpri = b_pri_rpri.clone();
210            // Fill the `R_i`-specific weights.
211            for (d_u_j, (b_pri_rpri_j, b_pri_ri_rpri_j)) in d_u
212                .values()
213                .rows()
214                .into_iter()
215                .zip(b_pri_rpri.iter_mut().zip(b_pri_ri_rpri.iter_mut()))
216            {
217                // Get the parents values for the j-th rows.
218                let pri_j = x_pri_u.iter().map(|&j| d_u_j[j]).collect();
219                // Get the parents weights associated to each row.
220                *b_pri_rpri_j = p_pri_rpri.pf(&pri_j, &array![])?;
221                *b_pri_ri_rpri_j = p_pri_ri_rpri.pf(&pri_j, &array![])?;
222            }
223            // Compute the `R_i`-specific weights.
224            Ok(b_pri_rpri / b_pri_ri_rpri)
225        };
226
227        // Compute the weights associated to each `R_i`.
228        let mut beta = Array::ones(d_u.values().nrows());
229        for (ri, pri) in pr_iter {
230            let beta_i = beta_i(d_u, ri, pri)?;
231            beta *= &beta_i;
232        }
233
234        // Rescale the weights.
235        if beta.sum() > 0. {
236            beta *= (beta.len() as f64) / beta.sum();
237        }
238
239        Ok(beta)
240    }
241
242    fn lw_deletion(&self) -> Result<Self::Complete> {
243        // Allocate new values.
244        let mut new_values = Array::zeros((
245            self.missing.complete_rows_count(), //
246            self.values.ncols(),
247        ));
248
249        // Get complete rows.
250        let rows = self
251            .values
252            .rows()
253            .into_iter()
254            .filter(|row| row.iter().all(|&x| !x.is_nan()));
255
256        // Filter valid rows.
257        new_values
258            .rows_mut()
259            .into_iter()
260            .zip(rows)
261            .for_each(|(mut new_row, row)| new_row.assign(&row));
262
263        // Return the complete dataset.
264        Self::Complete::new(self.labels.clone(), new_values)
265    }
266
267    fn pw_deletion(&self, x: &Set<usize>) -> Result<Self::Complete> {
268        // If no columns are specified, return an empty dataset.
269        if x.is_empty() {
270            let stats = labels![];
271            let v = Array::default((0, 0));
272            return GaussTable::new(stats, v);
273        }
274
275        // Check that the indices are valid.
276        x.iter().try_for_each(|&i| {
277            if i >= self.values.ncols() {
278                return Err(Error::IndexOutOfBounds(i));
279            }
280            Ok(())
281        })?;
282
283        // Clone the indices.
284        let mut cols: Vec<usize> = x.iter().cloned().collect();
285        // Sort the indices.
286        cols.sort();
287
288        // Get the indices of complete rows for the specified columns.
289        let rows: Vec<_> = self
290            .values
291            .rows()
292            .into_iter()
293            .enumerate()
294            .filter_map(|(i, row)| {
295                // Check if all specified columns are not missing.
296                if cols.iter().all(|&j| !row[j].is_nan()) {
297                    Some(i)
298                } else {
299                    None
300                }
301            })
302            .collect();
303
304        // Collect the values for the specified rows and columns.
305        let new_values = Array::from_shape_fn(
306            (rows.len(), cols.len()), //
307            |(i, j)| self.values[[rows[i], cols[j]]],
308        );
309
310        // Select the labels for the specified columns.
311        let new_labels = cols
312            .iter()
313            .map(|&j| {
314                self.labels
315                    .get_index(j)
316                    .cloned()
317                    .ok_or_else(|| Error::IndexOutOfBounds(j))
318            })
319            .collect::<Result<_>>()?;
320
321        // Return the complete dataset.
322        Self::Complete::new(new_labels, new_values)
323    }
324
325    fn ipw_deletion(&self, x: &Set<usize>, pr: &MissingMechanism) -> Result<Self::Weighted> {
326        // If no columns are specified, return an empty dataset.
327        if x.is_empty() {
328            let stats = labels![];
329            let v = Array::default((0, 0));
330            let w = Array::default(0);
331            return Self::Weighted::new(Self::Complete::new(stats, v)?, w);
332        }
333
334        // Check that the indices are valid.
335        x.iter().try_for_each(|&i| {
336            if i >= self.values.ncols() {
337                return Err(Error::IndexOutOfBounds(i));
338            }
339            Ok(())
340        })?;
341        // Check that the missing mechanism indices are valid.
342        pr.keys().try_for_each(|&i| {
343            if i >= self.values.ncols() {
344                return Err(Error::IndexOutOfBounds(i));
345            }
346            Ok(())
347        })?;
348        // Check that the missing mechanism is sorted.
349        if !pr.keys().is_sorted() {
350            return Err(Error::InvalidParameter(
351                "missing_mechanism",
352                "keys must be sorted.",
353            ));
354        }
355        if !pr.values().all(|pri| pri.iter().is_sorted()) {
356            return Err(Error::InvalidParameter(
357                "missing_mechanism",
358                "values must be sorted.",
359            ));
360        }
361
362        // Compute U recursively from X and Pi_R following the IPW algorithm.
363        let mut u = x.clone();
364        let mut pru: Set<_> = x
365            .iter()
366            .flat_map(|&x| pr.get(&x).cloned())
367            .flatten()
368            .collect();
369        // Compute the transitive closure of the parents.
370        while !pru.is_subset(&u) {
371            u.extend(pru.drain(..));
372            pru.extend(u.iter().flat_map(|&u| pr.get(&u).cloned()).flatten());
373        }
374        // Sort U.
375        u.sort();
376
377        // Apply pairwise deletion.
378        let d_u = self.pw_deletion(&u)?;
379        // Compute the weights w.r.t. pairwise deleted dataset.
380        let b_u = self.ipw_weights(&d_u, &u, pr)?;
381
382        // Map the indices to the restricted dataset.
383        let x = d_u.indices_from(x, self.labels())?;
384        // Since U is a superset of X, restrict U to X.
385        let d_x = d_u.select(&x)?;
386
387        // Return new weighted dataset.
388        Self::Weighted::new(d_x, b_u)
389    }
390
391    fn aipw_deletion(&self, x: &Set<usize>, pr: &MissingMechanism) -> Result<Self::Weighted> {
392        // If no columns are specified, return an empty dataset.
393        if x.is_empty() {
394            let l = labels![];
395            let v = Array::default((0, 0));
396            let w = Array::default(0);
397            return Self::Weighted::new(Self::Complete::new(l, v)?, w);
398        }
399
400        // Check that the indices are valid.
401        x.iter().try_for_each(|&i| {
402            if i >= self.values.ncols() {
403                return Err(Error::IndexOutOfBounds(i));
404            }
405            Ok(())
406        })?;
407        // Check that the missing mechanism indices are valid.
408        pr.keys().try_for_each(|&i| {
409            if i >= self.values.ncols() {
410                return Err(Error::IndexOutOfBounds(i));
411            }
412            Ok(())
413        })?;
414        // Check that the missing mechanism is sorted.
415        if !pr.keys().is_sorted() {
416            return Err(Error::InvalidParameter(
417                "missing_mechanism",
418                "keys must be sorted.",
419            ));
420        }
421        if !pr.values().all(|pri| pri.iter().is_sorted()) {
422            return Err(Error::InvalidParameter(
423                "missing_mechanism",
424                "values must be sorted.",
425            ));
426        }
427
428        // Compute W recursively from X and Pi_R following the IPW algorithm.
429        let mut w = x.clone();
430        let prw: Set<_> = x
431            .iter()
432            .flat_map(|x| pr.get(x).cloned())
433            .flatten()
434            .collect();
435        // Sort W.
436        w.sort();
437
438        // Get the set of partially observed variables.
439        let v_m = self.missing().partially_observed();
440        // Check if the intersection of Pi_R_W and V_M is empty.
441        if (&(&prw - &w) & v_m).is_empty() {
442            return self.ipw_deletion(x, pr); // ... IPW.
443        };
444
445        // Otherwise, apply pairwise deletion w.r.t. X.
446        let d_x = self.pw_deletion(x)?;
447        let b_x = Array::ones(d_x.values().nrows()); // ... aIPW.
448        // Return new weighted dataset.
449        Self::Weighted::new(d_x, b_x)
450    }
451}
452
453impl CsvIO for GaussIncTable {
454    fn from_csv_reader<R: Read>(reader: R) -> Result<Self> {
455        // Create a CSV reader from the string.
456        let mut reader = ReaderBuilder::new().has_headers(true).from_reader(reader);
457
458        // Check if the reader has headers.
459        if !reader.has_headers() {
460            return Err(Error::MissingHeader());
461        }
462
463        // Read the headers.
464        let labels: Labels = reader
465            .headers()?
466            .into_iter()
467            .map(|x| x.to_owned())
468            .collect();
469
470        // Read the records.
471        let values: Vec<GaussType> =
472            reader
473                .into_records()
474                .try_fold(Vec::new(), |mut values, row| -> Result<_> {
475                    // Get the record row.
476                    let row = row.map_err(|evidence| Error::Csv(Arc::new(evidence)))?;
477                    // Extend the values.
478                    values.extend(
479                        row.iter()
480                            .map(|x| x.parse::<GaussType>().unwrap_or(Self::MISSING)),
481                    );
482                    Ok(values)
483                })?;
484
485        // Convert values to an array.
486        let values = Array1::from_vec(values);
487
488        // Get the number of rows and columns.
489        let ncols = labels.len();
490        let nrows = values.len() / ncols;
491        // Reshape the values to the correct shape.
492        let values = values.into_shape_with_order((nrows, ncols))?;
493
494        // Construct the dataset.
495        Self::new(labels, values)
496    }
497
498    fn to_csv_writer<W: Write>(&self, writer: W) -> Result<()> {
499        // Create the CSV writer.
500        let mut writer = WriterBuilder::new().has_headers(true).from_writer(writer);
501
502        // Write the headers.
503        writer.write_record(self.labels.iter())?;
504
505        // Write the records.
506        for row in self.values.rows() {
507            // Map the row values to strings.
508            let record = row.iter().map(|&x| {
509                if x.is_nan() {
510                    "".to_string()
511                } else {
512                    x.to_string()
513                }
514            });
515            // Write the record.
516            writer.write_record(record)?;
517        }
518
519        Ok(())
520    }
521}