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}