Skip to main content

causal_hub/datasets/table/categorical/
complete.rs

1use std::{
2    borrow::Cow,
3    fmt::Display,
4    io::{Read, Write},
5    sync::Arc,
6};
7
8use csv::{ReaderBuilder, WriterBuilder};
9use itertools::Itertools;
10use log::debug;
11use ndarray::prelude::*;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    datasets::{CatEv, CatEvT, Dataset},
16    io::CsvIO,
17    models::{CatSupport, HasLabels},
18    types::{Error, Labels, Result, Set},
19};
20
21/// A type alias for a categorical variable.
22pub type CatType = u8;
23/// A type alias for a categorical sample.
24pub type CatSample = Array1<CatType>;
25
26/// A struct representing a categorical dataset.
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
28pub struct CatTable {
29    labels: Labels,
30    support: CatSupport,
31    shape: Array1<usize>,
32    values: Array2<CatType>,
33}
34
35/// Concrete iterator over categorical table evidences.
36pub struct CatTableEvidenceIter<'a> {
37    rows: ndarray::iter::LanesIter<'a, CatType, Ix1>,
38    support: &'a CatSupport,
39}
40
41impl<'a> Iterator for CatTableEvidenceIter<'a> {
42    type Item = Result<CatEv>;
43
44    fn next(&mut self) -> Option<Self::Item> {
45        let row = self.rows.next()?;
46
47        let evidences = row
48            .iter()
49            .enumerate()
50            .map(|(event, &state)| CatEvT::CertainPositive {
51                event,
52                state: state as usize,
53            });
54
55        Some(CatEv::new(self.support.clone(), evidences))
56    }
57}
58
59impl HasLabels for CatTable {
60    #[inline]
61    fn labels(&self) -> &Labels {
62        &self.labels
63    }
64}
65
66impl CatTable {
67    /// Creates a new categorical dataset.
68    ///
69    /// # Arguments
70    ///
71    /// * `support` - The variables support.
72    /// * `values` - The values of the variables.
73    ///
74    /// # Notes
75    ///
76    /// * Labels and support will be sorted in alphabetical order.
77    ///
78    /// # Errors
79    ///
80    /// * If the number of variable support is higher than `CatType::MAX`.
81    /// * If the number of variables is different from the number of values columns.
82    /// * If the variables values are not smaller than the number of support.
83    ///
84    /// # Panics
85    ///
86    /// * If the variable labels are not unique.
87    /// * If the variable support are not unique.
88    ///
89    /// # Returns
90    ///
91    /// A new categorical dataset instance.
92    ///
93    pub fn new(mut support: CatSupport, mut values: Array2<CatType>) -> Result<Self> {
94        // Log the creation of the categorical dataset.
95        debug!(
96            "Creating a new categorical dataset with {} variables and {} samples.",
97            support.len(),
98            values.nrows()
99        );
100
101        // Check if the number of support is less than `CatType::MAX`.
102        support.iter().try_for_each(|(label, state)| {
103            if state.len() > CatType::MAX as usize {
104                return Err(Error::InvalidParameter(
105                    &format!("support[{label}]"),
106                    &format!("should have less than 256 support, found {}", state.len()),
107                ));
108            }
109            Ok(())
110        })?;
111        // Check if the number of variables is equal to the number of columns.
112        if support.len() != values.ncols() {
113            return Err(Error::IncompatibleShape(
114                &format!("|support| = {}", support.len()),
115                &format!("|cols| = {}", values.ncols()),
116            ));
117        }
118        // Check if the maximum value of the values is less than the number of support.
119        values
120            .fold_axis(Axis(0), 0, |&a, &b| if a > b { a } else { b })
121            .into_iter()
122            .enumerate()
123            .try_for_each(|(i, x)| {
124                let (label, support) = support
125                    .get_index(i)
126                    .ok_or_else(|| Error::IndexOutOfBounds(i))?;
127
128                if x >= support.len() as CatType {
129                    return Err(Error::InvalidParameter(
130                        &format!("values[.., '{label}']"),
131                        &format!(
132                            "must be less than the number of support ({}), found {x}",
133                            support.len()
134                        ),
135                    ));
136                }
137                Ok(())
138            })?;
139
140        // Check that the labels are sorted.
141        if !support.keys().is_sorted() {
142            // Allocate indices to sort labels.
143            let mut indices: Vec<usize> = (0..support.len()).collect();
144            // Sort the indices by labels.
145            let keys: Vec<_> = support.keys().collect();
146            indices.sort_by_key(|&i| keys[i]);
147            // Sort the support.
148            support.sort_keys();
149            // Allocate new values.
150            let mut new_values = values.clone();
151            // Sort the new values according to the sorted indices.
152            indices.into_iter().enumerate().for_each(|(i, j)| {
153                new_values.column_mut(i).assign(&values.column(j));
154            });
155            // Update values.
156            values = new_values;
157        }
158
159        // For each variable ...
160        values
161            .columns_mut()
162            .into_iter()
163            .zip(support.values_mut())
164            .try_for_each(|(mut col, support)| -> Result<_> {
165                // ... check if the support are sorted.
166                if !support.is_sorted() {
167                    // Clone the support.
168                    let mut new_states = support.clone();
169                    // Sort the support.
170                    new_states.sort();
171                    // Map values to sorted support.
172                    col.iter_mut().try_for_each(|value| -> Result<_> {
173                        // Get the state.
174                        let state = &support[*value as usize];
175                        // Map the value to the sorted support.
176                        *value = new_states
177                            .get_index_of(state)
178                            .ok_or_else(|| Error::MissingState(state))?
179                            as CatType;
180                        Ok(())
181                    })?;
182                    // Update the support.
183                    *support = new_states;
184                }
185                Ok(())
186            })?;
187
188        // Get the labels of the variables.
189        let labels = support.keys().cloned().collect();
190        // Get the shape of the support.
191        let shape = support.values().map(Set::len).collect();
192
193        Ok(Self {
194            labels,
195            support,
196            shape,
197            values,
198        })
199    }
200
201    /// Returns the support of the variables in the categorical distribution.
202    ///
203    /// # Returns
204    ///
205    /// A reference to the vector of support.
206    ///
207    #[inline]
208    pub const fn support(&self) -> &CatSupport {
209        &self.support
210    }
211
212    /// Returns the shape of the set of support in the categorical distribution.
213    ///
214    /// # Returns
215    ///
216    /// A reference to the array of shape.
217    ///
218    #[inline]
219    pub const fn shape(&self) -> &Array1<usize> {
220        &self.shape
221    }
222}
223
224impl Display for CatTable {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        // Get the maximum length of the labels and support.
227        let n = self
228            .labels()
229            .iter()
230            .chain(self.support().values().flatten())
231            .map(|x| x.len())
232            .max()
233            .unwrap_or(0);
234
235        // Write the top line.
236        let hline = std::iter::repeat_n("-", (n + 3) * self.labels().len() + 1).join("");
237        writeln!(f, "{hline}")?;
238        // Write the header.
239        let header = self.labels().iter().map(|x| format!("{x:n$}")).join(" | ");
240        writeln!(f, "| {header} |")?;
241        // Write the separator.
242        let separator = (0..self.labels().len()).map(|_| "-".repeat(n)).join(" | ");
243        writeln!(f, "| {separator} |")?;
244        // Write the values.
245        for row in self.values.rows() {
246            // Get the state corresponding to the value.
247            let row = row
248                .iter()
249                .enumerate()
250                .map(|(i, &x)| &self.support()[i][x as usize])
251                .map(|x| format!("{x:n$}"))
252                .join(" | ");
253            writeln!(f, "| {row} |")?;
254        }
255        // Write the bottom line.
256        writeln!(f, "{hline}")
257    }
258}
259
260impl Dataset for CatTable {
261    type Values = Array2<CatType>;
262    type Support = CatSupport;
263    type Evidence = CatEv;
264    type EvidenceIter<'a> = CatTableEvidenceIter<'a>;
265
266    #[inline]
267    fn values(&self) -> &Self::Values {
268        &self.values
269    }
270
271    #[inline]
272    fn support(&self) -> Cow<'_, Self::Support> {
273        Cow::Borrowed(&self.support)
274    }
275
276    fn evidence_iter(&self) -> Self::EvidenceIter<'_> {
277        CatTableEvidenceIter {
278            rows: self.values.rows().into_iter(),
279            support: &self.support,
280        }
281    }
282
283    #[inline]
284    fn sample_size(&self) -> f64 {
285        self.values.nrows() as f64
286    }
287
288    fn select(&self, x: &Set<usize>) -> Result<Self> {
289        // Check that the indices are valid.
290        x.iter().try_for_each(|&i| {
291            if i >= self.values.ncols() {
292                return Err(Error::IndexOutOfBounds(i));
293            }
294            Ok(())
295        })?;
296
297        // Select the support.
298        let support: CatSupport = x
299            .iter()
300            .map(|&i| {
301                self.support
302                    .get_index(i)
303                    .map(|(label, support)| (label.clone(), support.clone()))
304                    .ok_or_else(|| Error::IndexOutOfBounds(i))
305            })
306            .collect::<Result<_>>()?;
307
308        // Select the values.
309        let mut new_values = Array2::zeros((self.values.nrows(), x.len()));
310        // Copy the selected columns.
311        x.iter().enumerate().for_each(|(j, &i)| {
312            new_values.column_mut(j).assign(&self.values.column(i));
313        });
314        // Update the values.
315        let values = new_values;
316
317        // Return the new dataset.
318        Self::new(support, values)
319    }
320}
321
322impl CsvIO for CatTable {
323    fn from_csv_reader<R: Read>(reader: R) -> Result<Self> {
324        // Create a CSV reader from the string.
325        let mut reader = ReaderBuilder::new().has_headers(true).from_reader(reader);
326
327        // Check if the reader has headers.
328        if !reader.has_headers() {
329            return Err(Error::MissingHeader());
330        }
331
332        // Read the headers.
333        let labels: Labels = reader
334            .headers()?
335            .into_iter()
336            .map(|x| x.to_owned())
337            .collect();
338
339        // Get the support of the variables.
340        let mut support: CatSupport = labels
341            .iter()
342            .map(|x| (x.clone(), Default::default()))
343            .collect();
344
345        // Read the records.
346        let values: Vec<CatType> = reader.into_records().enumerate().try_fold(
347            Vec::new(),
348            |mut values, (i, row)| -> Result<_> {
349                // Get the record row.
350                let row = row.map_err(|evidence| Error::Csv(Arc::new(evidence)))?;
351                // Zip the row with the support.
352                for (j, (x, support)) in row.into_iter().zip(support.values_mut()).enumerate() {
353                    // Check if the value is empty.
354                    if x.is_empty() {
355                        return Err(Error::MissingValue(i + 1, j + 1));
356                    }
357                    // Insert the value into the support, if not present.
358                    let (idx, _) = support.insert_full(x.to_owned());
359                    // Collect the value.
360                    values.push(idx as CatType);
361                }
362
363                Ok(values)
364            },
365        )?;
366
367        // Convert the values to an array.
368        let values = Array1::from_vec(values);
369
370        // Get the number of rows and columns.
371        let ncols = labels.len();
372        let nrows = values.len() / ncols;
373        // Reshape the values to the correct shape.
374        let values = values.into_shape_with_order((nrows, ncols))?;
375
376        // Construct the dataset.
377        Self::new(support, values)
378    }
379
380    fn to_csv_writer<W: Write>(&self, writer: W) -> Result<()> {
381        // Create the CSV writer.
382        let mut writer = WriterBuilder::new().has_headers(true).from_writer(writer);
383
384        // Write the headers.
385        writer.write_record(self.labels.iter())?;
386
387        // Write the records.
388        for row in self.values.rows() {
389            // Map the row values to support.
390            let record = row
391                .iter()
392                .zip(self.support().values())
393                .map(|(&x, support)| &support[x as usize]);
394            // Write the record.
395            writer.write_record(record)?;
396        }
397
398        Ok(())
399    }
400}