ndarray_stats/histogram/
histograms.rs

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