causal_hub/datasets/missing.rs
1use std::{
2 fmt::{Display, Formatter},
3 ops::Index,
4};
5
6use itertools::{Either, Itertools};
7use ndarray::prelude::*;
8use ndarray_stats::CorrelationExt;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 datasets::Dataset,
13 models::HasLabels,
14 types::{Error, Labels, Map, Result, Set},
15};
16
17/// A struct representing the missing data indicators.
18#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
19pub struct MissingMechanism {
20 labels: Labels,
21 pr: Map<usize, Set<usize>>,
22}
23
24impl MissingMechanism {
25 /// Create a new missing mechanism.
26 pub fn new(labels: Labels, mut pr: Map<usize, Set<usize>>) -> Result<Self> {
27 // Check if all indices are within bounds.
28 let n = labels.len();
29 for (&x, ys) in &pr {
30 if x >= n {
31 return Err(Error::IndexOutOfBounds(x));
32 }
33 for &y in ys {
34 if y >= n {
35 return Err(Error::IndexOutOfBounds(y));
36 }
37 }
38 }
39
40 // Sort the missing mechanism.
41 pr.sort_keys();
42 pr.iter_mut().for_each(|(_, ys)| ys.sort());
43
44 Ok(Self { labels, pr })
45 }
46
47 /// Returns the number of missing variables.
48 pub fn len(&self) -> usize {
49 self.pr.len()
50 }
51
52 /// Checks if the missing mechanism is empty.
53 pub fn is_empty(&self) -> bool {
54 self.pr.is_empty()
55 }
56
57 /// Returns the missing variables.
58 pub fn keys(&self) -> impl Iterator<Item = &usize> {
59 self.pr.keys()
60 }
61
62 /// Returns the missingness parents.
63 pub fn values(&self) -> impl Iterator<Item = &Set<usize>> {
64 self.pr.values()
65 }
66
67 /// Checks if a variable is missing.
68 pub fn contains_key(&self, x: &usize) -> bool {
69 self.pr.contains_key(x)
70 }
71
72 /// Returns the missingness parents for a given variable.
73 pub fn get(&self, x: &usize) -> Option<&Set<usize>> {
74 self.pr.get(x)
75 }
76
77 /// Inserts a missing variable and its missingness parents.
78 pub fn insert(&mut self, x: usize, mut y: Set<usize>) {
79 // Sort the missingness parents.
80 y.sort();
81 // Insert in sorted order.
82 self.pr.insert_sorted(x, y);
83 }
84}
85
86impl HasLabels for MissingMechanism {
87 #[inline]
88 fn labels(&self) -> &Labels {
89 &self.labels
90 }
91}
92
93impl Index<usize> for MissingMechanism {
94 type Output = Set<usize>;
95
96 fn index(&self, index: usize) -> &Self::Output {
97 &self.pr[&index]
98 }
99}
100
101impl IntoIterator for MissingMechanism {
102 type Item = (usize, Set<usize>);
103 type IntoIter = indexmap::map::IntoIter<usize, Set<usize>>;
104
105 fn into_iter(self) -> Self::IntoIter {
106 self.pr.into_iter()
107 }
108}
109
110impl<'a> IntoIterator for &'a MissingMechanism {
111 type Item = (&'a usize, &'a Set<usize>);
112 type IntoIter = indexmap::map::Iter<'a, usize, Set<usize>>;
113
114 fn into_iter(self) -> Self::IntoIter {
115 self.pr.iter()
116 }
117}
118
119/// An enum representing different methods for handling missing data.
120#[non_exhaustive]
121#[derive(Clone, Copy, Debug)]
122pub enum MissingMethod {
123 /// List-wise deletion missing handling method.
124 LW,
125 /// Pair-wise deletion missing handling method.
126 PW,
127 /// Inverse probability weighting missing handling method.
128 IPW,
129 /// Augmented inverse probability weighting missing handling method.
130 AIPW,
131}
132
133/// Missing mechanism t types.
134#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
135pub enum MissingType {
136 /// Missing Completely At Random.
137 MCAR,
138 /// Missing At Random.
139 MAR,
140 /// Missing Not At Random.
141 MNAR,
142}
143
144impl Display for MissingType {
145 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146 match self {
147 Self::MCAR => write!(f, "MCAR"),
148 Self::MAR => write!(f, "MAR"),
149 Self::MNAR => write!(f, "MNAR"),
150 }
151 }
152}
153
154/// A struct for missing information in a tabular dataset.
155#[derive(Clone, Debug, Serialize, Deserialize)]
156pub struct MissingTable {
157 labels: Labels,
158 fully_observed: Set<usize>,
159 partially_observed: Set<usize>,
160 missing_mask: Array2<bool>,
161 missing_mask_by_cols: Array1<bool>,
162 missing_mask_by_rows: Array1<bool>,
163 missing_count: usize,
164 missing_count_by_cols: Array1<usize>,
165 missing_count_by_rows: Array1<usize>,
166 missing_rate: f64,
167 missing_rate_by_cols: Array1<f64>,
168 missing_rate_by_rows: Array1<f64>,
169 missing_correlation: Array2<f64>,
170 missing_covariance: Array2<f64>,
171 complete_cols_count: usize,
172 complete_rows_count: usize,
173}
174
175impl HasLabels for MissingTable {
176 #[inline]
177 fn labels(&self) -> &Labels {
178 &self.labels
179 }
180}
181
182impl MissingTable {
183 /// Create a new missing information table from the given labels and missing mask.
184 ///
185 /// # Arguments
186 ///
187 /// * `labels` - The labels of the dataset.
188 /// * `missing_mask` - A boolean matrix indicating missing values.
189 ///
190 /// # Returns
191 ///
192 /// A new missing information instance.
193 ///
194 pub fn new(mut labels: Labels, mut missing_mask: Array2<bool>) -> Result<Self> {
195 // Check if dimensions match.
196 if labels.len() != missing_mask.ncols() {
197 return Err(Error::IncompatibleShape(
198 &format!("|labels| = {}", labels.len()),
199 &format!("|cols| = {}", missing_mask.ncols()),
200 ));
201 }
202
203 // Check if labels are sorted.
204 if !labels.is_sorted() {
205 // Allocate indices to sort labels.
206 let mut indices: Vec<usize> = (0..labels.len()).collect();
207 // Sort the indices by labels.
208 indices.sort_by_key(|&i| &labels[i]);
209 // Sort the labels.
210 labels.sort();
211 // Allocate new missing mask.
212 let mut new_missing_mask = missing_mask.clone();
213 // Sort the new missing mask according to the sorted indices.
214 indices.into_iter().enumerate().for_each(|(i, j)| {
215 new_missing_mask
216 .column_mut(i)
217 .assign(&missing_mask.column(j));
218 });
219 // Update missing mask.
220 missing_mask = new_missing_mask;
221 }
222
223 // Compute missing counts.
224 let missing_count_by_cols = missing_mask.mapv(|x| x as usize).sum_axis(Axis(0));
225 let missing_count_by_rows = missing_mask.mapv(|x| x as usize).sum_axis(Axis(1));
226 let missing_count = missing_count_by_cols.sum();
227
228 // Compute missing mask by cols and rows.
229 let missing_mask_by_cols = missing_count_by_cols.mapv(|x| x > 0);
230 let missing_mask_by_rows = missing_count_by_rows.mapv(|x| x > 0);
231
232 // Compute fully and partially observed variable sets.
233 let (fully_observed, partially_observed) = missing_mask_by_cols
234 .iter()
235 .enumerate()
236 .partition_map(|(i, &x)| {
237 if !x {
238 Either::Left(i)
239 } else {
240 Either::Right(i)
241 }
242 });
243
244 // Compute complete counts.
245 let complete_cols_count = missing_mask_by_cols.mapv(|x| (!x) as usize).sum();
246 let complete_rows_count = missing_mask_by_rows.mapv(|x| (!x) as usize).sum();
247
248 // Compute missing rates.
249 let missing_rate_by_cols =
250 missing_count_by_cols.mapv(|x| x as f64) / missing_mask.nrows() as f64;
251 let missing_rate_by_rows =
252 missing_count_by_rows.mapv(|x| x as f64) / missing_mask.ncols() as f64;
253 let missing_rate = missing_count as f64 / missing_mask.len() as f64;
254
255 // TODO: Make this optional for large datasets.
256 // Map to numeric (float) mask.
257 let missing_mask_numeric = missing_mask.mapv(|x| x as u8 as f64);
258 // Transpose for correlation/covariance computation.
259 let missing_mask_numeric = missing_mask_numeric.t();
260 // Compute missing correlation.
261 let missing_correlation = missing_mask_numeric
262 .pearson_correlation()
263 .map_err(|evidence| Error::Stats(&evidence.to_string()))?;
264 // Compute missing covariance.
265 let missing_covariance = missing_mask_numeric
266 .cov(1.)
267 .map_err(|evidence| Error::Stats(&evidence.to_string()))?;
268
269 Ok(Self {
270 labels,
271 fully_observed,
272 partially_observed,
273 missing_mask,
274 missing_mask_by_cols,
275 missing_mask_by_rows,
276 missing_count,
277 missing_count_by_cols,
278 missing_count_by_rows,
279 missing_rate,
280 missing_rate_by_cols,
281 missing_rate_by_rows,
282 missing_correlation,
283 missing_covariance,
284 complete_cols_count,
285 complete_rows_count,
286 })
287 }
288
289 /// Get the set of fully observed variables.
290 ///
291 /// # Returns
292 ///
293 /// A reference to the set of fully observed variables.
294 ///
295 #[inline]
296 pub const fn fully_observed(&self) -> &Set<usize> {
297 &self.fully_observed
298 }
299
300 /// Get the set of partially observed variables.
301 ///
302 /// # Returns
303 ///
304 /// A reference to the set of partially observed variables.
305 ///
306 #[inline]
307 pub const fn partially_observed(&self) -> &Set<usize> {
308 &self.partially_observed
309 }
310
311 /// Get the missing mask indicating the presence of missing values in the table.
312 ///
313 /// # Returns
314 ///
315 /// A reference to the missing mask.
316 ///
317 #[inline]
318 pub const fn missing_mask(&self) -> &Array2<bool> {
319 &self.missing_mask
320 }
321
322 /// Get the missing mask indicating the presence of missing values in each column.
323 ///
324 /// # Returns
325 ///
326 /// A reference to the missing mask by columns.
327 ///
328 #[inline]
329 pub const fn missing_mask_by_cols(&self) -> &Array1<bool> {
330 &self.missing_mask_by_cols
331 }
332
333 /// Get the missing mask indicating the presence of missing values in each row.
334 ///
335 /// # Returns
336 ///
337 /// A reference to the missing mask by rows.
338 ///
339 #[inline]
340 pub const fn missing_mask_by_rows(&self) -> &Array1<bool> {
341 &self.missing_mask_by_rows
342 }
343
344 /// Get the total count of missing values in the table.
345 ///
346 /// # Returns
347 ///
348 /// The count of missing values.
349 ///
350 #[inline]
351 pub const fn missing_count(&self) -> usize {
352 self.missing_count
353 }
354
355 /// Get the count of missing values in each column.
356 ///
357 /// # Returns
358 ///
359 /// A reference to the missing count by columns.
360 ///
361 #[inline]
362 pub const fn missing_count_by_cols(&self) -> &Array1<usize> {
363 &self.missing_count_by_cols
364 }
365
366 /// Get the count of missing values in each row.
367 ///
368 /// # Returns
369 ///
370 /// A reference to the missing count by rows.
371 ///
372 #[inline]
373 pub const fn missing_count_by_rows(&self) -> &Array1<usize> {
374 &self.missing_count_by_rows
375 }
376
377 /// Get the overall missing rate in the table.
378 ///
379 /// # Returns
380 ///
381 /// The percentage of missing values.
382 ///
383 #[inline]
384 pub const fn missing_rate(&self) -> f64 {
385 self.missing_rate
386 }
387
388 /// Get the missing rate in each column.
389 ///
390 /// # Returns
391 ///
392 /// A reference to the missing percentage by columns.
393 ///
394 #[inline]
395 pub const fn missing_rate_by_cols(&self) -> &Array1<f64> {
396 &self.missing_rate_by_cols
397 }
398
399 /// Get the missing rate in each row.
400 ///
401 /// # Returns
402 ///
403 /// A reference to the missing percentage by rows.
404 ///
405 #[inline]
406 pub const fn missing_rate_by_rows(&self) -> &Array1<f64> {
407 &self.missing_rate_by_rows
408 }
409
410 /// Get the missing (Pearson) correlation matrix.
411 ///
412 /// # Returns
413 ///
414 /// A reference to the missing correlation matrix.
415 ///
416 #[inline]
417 pub const fn missing_correlation(&self) -> &Array2<f64> {
418 &self.missing_correlation
419 }
420
421 /// Get the missing (unbiased) covariance matrix.
422 ///
423 /// # Returns
424 ///
425 /// A reference to the missing covariance matrix.
426 ///
427 #[inline]
428 pub const fn missing_covariance(&self) -> &Array2<f64> {
429 &self.missing_covariance
430 }
431
432 /// Get the count of complete columns (without any missing values) in the table.
433 ///
434 /// # Returns
435 ///
436 /// The count of complete columns.
437 ///
438 #[inline]
439 pub const fn complete_cols_count(&self) -> usize {
440 self.complete_cols_count
441 }
442
443 /// Get the count of complete rows (without any missing values) in the table.
444 ///
445 /// # Returns
446 ///
447 /// The count of complete rows.
448 ///
449 #[inline]
450 pub const fn complete_rows_count(&self) -> usize {
451 self.complete_rows_count
452 }
453}
454
455/// A trait for incomplete datasets.
456pub trait IncDataset: Dataset + Sized {
457 /// The type of the missing data indicator.
458 type Missing;
459 /// The value of the missing data indicator.
460 const MISSING: Self::Missing;
461
462 /// The type of the complete dataset.
463 type Complete;
464 /// The type of the weighted dataset.
465 type Weighted;
466
467 /// Get the missing information.
468 ///
469 /// # Returns
470 ///
471 /// A reference to the missing information.
472 ///
473 fn missing(&self) -> &MissingTable;
474
475 /// Apply a missing data handling method to the dataset.
476 ///
477 /// # Arguments
478 ///
479 /// * `m` - The missing data handling method to apply.
480 /// * `x` - An optional set of variables to consider for missing data handling.
481 /// * `pr` - An optional missing mechanism specification.
482 ///
483 /// # Errors
484 ///
485 /// * If the set of variables to consider for missing data handling is empty.
486 /// * If any variable in the set is out of bounds.
487 ///
488 /// # Returns
489 ///
490 /// Either a complete or weighted dataset.
491 ///
492 fn apply_missing_method(
493 &self,
494 model: &MissingMethod,
495 x: Option<&Set<usize>>,
496 pr: Option<&MissingMechanism>,
497 ) -> Result<Either<Self::Complete, Self::Weighted>> {
498 // Get short alias for missing method.
499 use MissingMethod as MM;
500 // Apply the missing method with the provided arguments.
501 match (model, x, pr) {
502 (MM::LW, _, _) => self.lw_deletion().map(Either::Left),
503 (MM::PW, Some(x), _) => self.pw_deletion(x).map(Either::Left),
504 (MM::IPW, Some(x), Some(pr)) => self.ipw_deletion(x, pr).map(Either::Right),
505 (MM::AIPW, Some(x), Some(pr)) => self.aipw_deletion(x, pr).map(Either::Right),
506 _ => Err(Error::InvalidParameter(
507 "missing_method",
508 &format!(
509 "Invalid arguments for applying missing method:\n\
510 \t missing method: '{model:?}' , \n\
511 \t selected variables: '{x:?}' , \n\
512 \t missing mechanism: '{pr:?}' .",
513 ),
514 )),
515 }
516 }
517
518 /// Compute the weights to perform IPW.
519 fn ipw_weights(
520 &self,
521 d_u: &Self::Complete,
522 u: &Set<usize>,
523 pr: &MissingMechanism,
524 ) -> Result<Array1<f64>>;
525
526 /// Perform list-wise (LW) deletion to handle missing data.
527 ///
528 /// # Errors
529 ///
530 /// * If the dataset is empty after LW deletion.
531 ///
532 /// # Returns
533 ///
534 /// A complete dataset obtained via LW deletion.
535 ///
536 fn lw_deletion(&self) -> Result<Self::Complete>;
537
538 /// Perform pair-wise (PW) deletion to handle missing data for the specified columns.
539 ///
540 /// # Arguments
541 ///
542 /// * `x` - A set of column indices for PW deletion.
543 ///
544 /// # Errors
545 ///
546 /// * If the set of variables to consider for missing data handling is empty.
547 /// * If any variable in the set is out of bounds.
548 ///
549 /// # Returns
550 ///
551 /// A complete dataset restricted to the specified columns via PW deletion.
552 ///
553 fn pw_deletion(&self, x: &Set<usize>) -> Result<Self::Complete>;
554
555 /// Perform inverse probability weighting (IPW) deletion to handle missing data for the specified columns.
556 ///
557 /// # Arguments
558 ///
559 /// * `x` - A set of column indices for IPW deletion.
560 /// * `pr` - The missing data indicators.
561 ///
562 /// # Errors
563 ///
564 /// * If the set of variables to consider for missing data handling is empty.
565 /// * If any variable in the set is out of bounds.
566 ///
567 /// # Returns
568 ///
569 /// A weighted dataset restricted to the specified columns via IPW deletion.
570 ///
571 fn ipw_deletion(&self, x: &Set<usize>, pr: &MissingMechanism) -> Result<Self::Weighted>;
572
573 /// Perform augmented inverse probability weighting (AIPW) deletion to handle missing data for the specified columns.
574 ///
575 /// # Arguments
576 ///
577 /// * `x` - A set of column indices for AIPW deletion.
578 /// * `pr` - The missing data indicators.
579 ///
580 /// # Errors
581 ///
582 /// * If the set of variables to consider for missing data handling is empty.
583 /// * If any variable in the set is out of bounds.
584 ///
585 /// # Returns
586 ///
587 /// A weighted dataset restricted to the specified columns via AIPW deletion.
588 ///
589 fn aipw_deletion(&self, x: &Set<usize>, pr: &MissingMechanism) -> Result<Self::Weighted>;
590}