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}