ndarray_histogram/histogram/
histograms.rs

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