data_matrix/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc(html_root_url = "https://docs.rs/data-matrix/0.2.0")]
3
4//! # Two dimensional array indexed by string labels
5#![doc = include_str!("../README.rustdoc.md")]
6
7mod datamatrix_builder;
8mod errors;
9
10pub use crate::errors::Error;
11use crate::Error::IncorrectMatrixLabels;
12pub use datamatrix_builder::DataMatrixBuilder;
13
14/// A dense matrix of numeric values with labeled rows and columns.
15#[derive(Debug, Clone)]
16pub struct DataMatrix {
17    /// Matrix data: values indexed by (row, column).
18    data: Vec<Vec<f64>>,
19
20    /// Row labels (index -> label).
21    row_labels: Vec<String>,
22
23    /// Column labels (index -> label).
24    col_labels: Vec<String>,
25}
26
27impl DataMatrix {
28    /// Creates a new DataMatrix from data and labels.
29    ///
30    /// Results in an error if the data shape does not match the labels. In daily work you might prefer
31    /// to use [`DataMatrixBuilder`] to create a [`DataMatrix`] from a file or data.
32    pub fn new(
33        data: Vec<Vec<f64>>,
34        row_labels: Vec<String>,
35        col_labels: Vec<String>,
36    ) -> Result<Self, Error> {
37        if data.len() != row_labels.len() {
38            return Err(IncorrectMatrixLabels {
39                expected: row_labels.len(),
40                actual: data.len(),
41            });
42        }
43        if data.is_empty() || data[0].len() != col_labels.len() {
44            return Err(IncorrectMatrixLabels {
45                expected: col_labels.len(),
46                actual: data[0].len(),
47            });
48        }
49
50        Ok(Self {
51            data,
52            row_labels,
53            col_labels,
54        })
55    }
56
57    /// Returns the number of rows.
58    pub fn nrows(&self) -> usize {
59        self.data.len()
60    }
61
62    /// Returns the number of columns.
63    pub fn ncols(&self) -> usize {
64        if let Some(first_row) = self.data.first() {
65            first_row.len()
66        } else {
67            0
68        }
69    }
70
71    /// Gets the matrix entry at (i, j).
72    pub fn get(&self, i: usize, j: usize) -> Option<f64> {
73        self.data.get(i).and_then(|row| row.get(j)).copied()
74    }
75
76    /// Gets the matrix entry by row and column label.
77    pub fn get_by_label(&self, row_label: &str, col_label: &str) -> Option<f64> {
78        let row_idx = self.row_labels.iter().position(|r| r == row_label)?;
79        let col_idx = self.col_labels.iter().position(|c| c == col_label)?;
80        self.get(row_idx, col_idx)
81    }
82
83    /// Returns the label of a row by its index.
84    pub fn row_index(&self, label: &str) -> Option<usize> {
85        self.row_labels.iter().position(|r| r == label)
86    }
87
88    /// Returns the label of a column by its index.
89    pub fn col_index(&self, label: &str) -> Option<usize> {
90        self.col_labels.iter().position(|r| r == label)
91    }
92
93    /// Returns the label of a row by its index.
94    pub fn row_label(&self, index: usize) -> &String {
95        &self.row_labels[index]
96    }
97
98    /// Returns the label of a column by its index.
99    pub fn col_label(&self, index: usize) -> &String {
100        &self.col_labels[index]
101    }
102
103    /// Returns the row labels.
104    ///
105    /// If the matrix is symmetric, the row labels are the same as the column labels.
106    pub fn row_labels(&self) -> &[String] {
107        &self.row_labels
108    }
109
110    /// Returns the column labels.
111    ///
112    /// If the matrix is symmetric, the row labels are the same as the column labels.
113    pub fn col_labels(&self) -> &[String] {
114        &self.col_labels
115    }
116
117    /// Access the raw matrix data.
118    pub fn data(&self) -> &Vec<Vec<f64>> {
119        &self.data
120    }
121
122    /// Checks if the matrix is square.
123    pub fn is_square(&self) -> bool {
124        self.nrows() == self.ncols()
125    }
126}