ndarray_stats/histogram/
histograms.rs

1use super::errors::BinNotFound;
2use super::grid::Grid;
3use ndarray::prelude::*;
4
5/// Histogram data structure.
6pub struct Histogram<A: Ord> {
7    counts: ArrayD<usize>,
8    grid: Grid<A>,
9}
10
11impl<A: Ord> Histogram<A> {
12    /// Returns a new instance of Histogram given a [`Grid`].
13    ///
14    /// [`Grid`]: struct.Grid.html
15    pub fn new(grid: Grid<A>) -> Self {
16        let counts = ArrayD::zeros(grid.shape());
17        Histogram { counts, grid }
18    }
19
20    /// Adds a single observation to the histogram.
21    ///
22    /// **Panics** if dimensions do not match: `self.ndim() != observation.len()`.
23    ///
24    /// # Example:
25    /// ```
26    /// use ndarray::array;
27    /// use ndarray_stats::histogram::{Edges, Bins, Histogram, Grid};
28    /// use noisy_float::types::n64;
29    ///
30    /// let edges = Edges::from(vec![n64(-1.), n64(0.), n64(1.)]);
31    /// let bins = Bins::new(edges);
32    /// let square_grid = Grid::from(vec![bins.clone(), bins.clone()]);
33    /// let mut histogram = Histogram::new(square_grid);
34    ///
35    /// let observation = array![n64(0.5), n64(0.6)];
36    ///
37    /// histogram.add_observation(&observation)?;
38    ///
39    /// let histogram_matrix = histogram.counts();
40    /// let expected = array![
41    ///     [0, 0],
42    ///     [0, 1],
43    /// ];
44    /// assert_eq!(histogram_matrix, expected.into_dyn());
45    /// # Ok::<(), Box<std::error::Error>>(())
46    /// ```
47    pub fn add_observation(&mut self, observation: &ArrayRef<A, Ix1>) -> Result<(), BinNotFound> {
48        match self.grid.index_of(observation) {
49            Some(bin_index) => {
50                self.counts[&*bin_index] += 1;
51                Ok(())
52            }
53            None => Err(BinNotFound),
54        }
55    }
56
57    /// Returns the number of dimensions of the space the histogram is covering.
58    pub fn ndim(&self) -> usize {
59        debug_assert_eq!(self.counts.ndim(), self.grid.ndim());
60        self.counts.ndim()
61    }
62
63    /// Borrows a view on the histogram counts matrix.
64    pub fn counts(&self) -> ArrayViewD<'_, usize> {
65        self.counts.view()
66    }
67
68    /// Borrows an immutable reference to the histogram grid.
69    pub fn grid(&self) -> &Grid<A> {
70        &self.grid
71    }
72}
73
74/// Extension trait for `ArrayRef` providing methods to compute histograms.
75pub trait HistogramExt<A> {
76    /// Returns the [histogram](https://en.wikipedia.org/wiki/Histogram)
77    /// for a 2-dimensional array of points `M`.
78    ///
79    /// Let `(n, d)` be the shape of `M`:
80    /// - `n` is the number of points;
81    /// - `d` is the number of dimensions of the space those points belong to.
82    /// It follows that every column in `M` is a `d`-dimensional point.
83    ///
84    /// For example: a (3, 4) matrix `M` is a collection of 3 points in a
85    /// 4-dimensional space.
86    ///
87    /// Important: points outside the grid are ignored!
88    ///
89    /// **Panics** if `d` is different from `grid.ndim()`.
90    ///
91    /// # Example:
92    ///
93    /// ```
94    /// use ndarray::array;
95    /// use ndarray_stats::{
96    ///     HistogramExt,
97    ///     histogram::{
98    ///         Histogram, Grid, GridBuilder,
99    ///         Edges, Bins,
100    ///         strategies::Sqrt},
101    /// };
102    /// use noisy_float::types::{N64, n64};
103    ///
104    /// let observations = array![
105    ///     [n64(1.), n64(0.5)],
106    ///     [n64(-0.5), n64(1.)],
107    ///     [n64(-1.), n64(-0.5)],
108    ///     [n64(0.5), n64(-1.)]
109    /// ];
110    /// let grid = GridBuilder::<Sqrt<N64>>::from_array(&observations).unwrap().build();
111    /// let expected_grid = Grid::from(
112    ///     vec![
113    ///         Bins::new(Edges::from(vec![n64(-1.), n64(0.), n64(1.), n64(2.)])),
114    ///         Bins::new(Edges::from(vec![n64(-1.), n64(0.), n64(1.), n64(2.)])),
115    ///     ]
116    /// );
117    /// assert_eq!(grid, expected_grid);
118    ///
119    /// let histogram = observations.histogram(grid);
120    ///
121    /// let histogram_matrix = histogram.counts();
122    /// // Bins are left inclusive, right exclusive!
123    /// let expected = array![
124    ///     [1, 0, 1],
125    ///     [1, 0, 0],
126    ///     [0, 1, 0],
127    /// ];
128    /// assert_eq!(histogram_matrix, expected.into_dyn());
129    /// ```
130    fn histogram(&self, grid: Grid<A>) -> Histogram<A>
131    where
132        A: Ord;
133
134    private_decl! {}
135}
136
137impl<A> HistogramExt<A> for ArrayRef<A, Ix2>
138where
139    A: Ord,
140{
141    fn histogram(&self, grid: Grid<A>) -> Histogram<A> {
142        let mut histogram = Histogram::new(grid);
143        for point in self.axis_iter(Axis(0)) {
144            let _ = histogram.add_observation(&point);
145        }
146        histogram
147    }
148
149    private_impl! {}
150}