r2rs_nmath/distribution/pois/mod.rs
1mod d;
2mod p;
3mod q;
4mod r;
5
6use strafe_type::{LogProbability64, Positive64, Probability64, Real64};
7
8pub(crate) use self::{d::*, p::*, q::*, r::*};
9use crate::traits::{Distribution, RNG};
10
11/// # The Poisson Distribution
12///
13/// ## Description
14///
15/// Density, distribution function, quantile function and random generation for the Poisson
16/// distribution with parameter lambda.
17///
18/// ## Arguments
19///
20/// * lambda: (non-negative) means.
21///
22/// ## Details
23///
24/// The Poisson distribution has density
25///
26/// $ p(x) = \lambda^x \frac{exp(-\lambda)}{x!} $
27///
28/// for x = 0, 1, 2, … . The mean and variance are $ E(X) = Var(X) = \lambda $.
29///
30/// Note that $ \lambda = 0 $ is really a limit case (setting 0^0 = 1) resulting in a point mass at
31/// 0, see also the example.
32///
33/// If an element of x is not integer, the result of dpois is zero, with a warning. p(x) is
34/// computed using Loader's algorithm, see the reference in dbinom.
35///
36/// The quantile is right continuous: qpois(p, lambda) is the smallest integer x such that
37/// $P(X ≤ x) ≥ p$.
38///
39/// Setting lower.tail = FALSE allows to get much more precise results when the default,
40/// lower.tail = TRUE would return 1, see the example below.
41///
42/// ## Density Plot
43///
44/// ```rust
45/// # use r2rs_base::traits::StatisticalSlice;
46/// # use r2rs_nmath::{distribution::PoissonBuilder, traits::Distribution};
47/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
48/// # use strafe_type::FloatConstraint;
49/// let pois = PoissonBuilder::new().build();
50/// let x = <[f64]>::sequence_by(-1.0, 7.0, 0.001);
51/// let y = x
52/// .iter()
53/// .map(|x| pois.density(x).unwrap())
54/// .collect::<Vec<_>>();
55///
56/// let root = SVGBackend::new("density.svg", (1024, 768)).into_drawing_area();
57/// Plot::new()
58/// .with_options(PlotOptions {
59/// x_axis_label: "x".to_string(),
60/// y_axis_label: "density".to_string(),
61/// ..Default::default()
62/// })
63/// .with_plottable(Line {
64/// x,
65/// y,
66/// color: BLACK,
67/// ..Default::default()
68/// })
69/// .plot(&root)
70/// .unwrap();
71/// # use std::fs::rename;
72/// # drop(root);
73/// # rename(
74/// # format!("density.svg"),
75/// # format!("src/distribution/pois/doctest_out/density.svg"),
76/// # )
77/// # .unwrap();
78/// ```
79#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("density", "src/distribution/pois/doctest_out/density.svg")))]
80#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = "![Density][density]"))]
81///
82/// ## Source
83///
84/// dpois uses C code contributed by Catherine Loader (see dbinom).
85///
86/// ppois uses pgamma.
87///
88/// qpois uses the Cornish–Fisher Expansion to include a skewness correction to a normal
89/// approximation, followed by a search.
90///
91/// rpois uses
92///
93/// Ahrens, J. H. and Dieter, U. (1982). Computer generation of Poisson deviates from modified
94/// normal distributions. ACM Transactions on Mathematical Software, 8, 163–179.
95///
96/// ## See Also
97/// Distributions for other standard distributions, including dbinom for the binomial and dnbinom
98/// for the negative binomial distribution.
99///
100/// poisson.test.
101///
102/// ## Examples
103///
104/// // Should be 1
105/// ```rust
106/// # use r2rs_nmath::{
107/// # distribution::PoissonBuilder, func::gamma, traits::Distribution,
108/// # };
109/// # use strafe_type::FloatConstraint;
110/// let x = (0..=7).collect::<Vec<_>>();
111/// let pois = PoissonBuilder::new().with_lambda(1).build();
112/// let r = x
113/// .iter()
114/// .map(|x| -(pois.density(x).unwrap() * gamma(1 + x).unwrap().unwrap()).ln())
115/// .collect::<Vec<_>>();
116/// println!("{r:?}");
117/// # use std::{fs::File, io::Write};
118/// # let mut f = File::create("src/distribution/pois/doctest_out/dens.md").unwrap();
119/// # writeln!(f, "```output").unwrap();
120/// # writeln!(f, "{r:?}").unwrap();
121/// # writeln!(f, "```").unwrap();
122/// ```
123#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/dens.md")))]
124///
125/// ```rust
126/// # use std::collections::HashMap;
127/// #
128/// # use r2rs_nmath::{
129/// # distribution::PoissonBuilder,
130/// # rng::MersenneTwister,
131/// # traits::{Distribution, RNG},
132/// # };
133/// # use strafe_type::FloatConstraint;
134/// let pois = PoissonBuilder::new().with_lambda(4).build();
135/// let mut rng = MersenneTwister::new();
136/// rng.set_seed(1);
137/// let mut r = (0..50)
138/// .map(|_| pois.random_sample(&mut rng).unwrap() as usize)
139/// .fold(HashMap::new(), |mut acc, r| {
140/// *acc.entry(r).or_insert(0) += 1;
141/// acc
142/// })
143/// .into_iter()
144/// .collect::<Vec<_>>();
145/// r.sort_by(|(i1, _), (i2, _)| i1.cmp(i2));
146///
147/// for (key, index) in &r {
148/// println!("{key:2}: {index}");
149/// }
150/// # use std::{fs::File, io::Write};
151/// # let mut f = File::create("src/distribution/pois/doctest_out/table.md").unwrap();
152/// # writeln!(f, "```output").unwrap();
153/// # for (key, index) in &r {
154/// # writeln!(f, "{key:2}: {index}").unwrap();
155/// # }
156/// # writeln!(f, "```").unwrap();
157/// ```
158#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/table.md")))]
159///
160/// Using lower tail directly fixes the cancellation (values becoming 0)
161/// ```rust
162/// # use r2rs_nmath::{distribution::PoissonBuilder, traits::Distribution};
163/// # use strafe_type::FloatConstraint;
164/// let x = (15..=25).collect::<Vec<_>>();
165/// let pois = PoissonBuilder::new().with_lambda(100).build();
166/// let r1 = x
167/// .iter()
168/// .map(|x| 1.0 - pois.probability(x * 10, true).unwrap())
169/// .collect::<Vec<_>>();
170/// println!("{r1:?}");
171/// let r2 = x
172/// .iter()
173/// .map(|x| pois.probability(x * 10, false).unwrap())
174/// .collect::<Vec<_>>();
175/// println!("{r2:?}");
176/// # use std::{fs::File, io::Write};
177/// # let mut f = File::create("src/distribution/pois/doctest_out/prob.md").unwrap();
178/// # writeln!(f, "```output").unwrap();
179/// # writeln!(f, "{r1:?}").unwrap();
180/// # writeln!(f, "{r2:?}").unwrap();
181/// # writeln!(f, "```").unwrap();
182/// ```
183#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/prob.md")))]
184///
185/// ```rust
186/// # use r2rs_base::traits::StatisticalSlice;
187/// # use r2rs_nmath::{
188/// # distribution::{BinomialBuilder, PoissonBuilder},
189/// # traits::Distribution,
190/// # };
191/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
192/// # use strafe_type::FloatConstraint;
193/// let x = <[f64]>::sequence_by(-0.01, 5.0, 0.01);
194///
195/// let pois = PoissonBuilder::new().with_lambda(1).build();
196/// let y1 = x
197/// .iter()
198/// .map(|x| pois.probability(x, true).unwrap())
199/// .collect::<Vec<_>>();
200///
201/// let binom = BinomialBuilder::new()
202/// .with_size(100)
203/// .with_success_probability(0.01)
204/// .build();
205/// let y2 = x
206/// .iter()
207/// .map(|x| binom.probability(x, true).unwrap())
208/// .collect::<Vec<_>>();
209///
210/// let root = SVGBackend::new("prob_plots.svg", (1024, 768)).into_drawing_area();
211///
212/// Plot::new()
213/// .with_options(PlotOptions {
214/// x_axis_label: "x".to_string(),
215/// y_axis_label: "F(x)".to_string(),
216/// plot_bottom: 0.5,
217/// title: "Poisson(1) CDF".to_string(),
218/// ..Default::default()
219/// })
220/// .with_plottable(Line {
221/// x: x.clone(),
222/// y: y1,
223/// color: BLACK,
224/// ..Default::default()
225/// })
226/// .plot(&root)
227/// .unwrap();
228///
229/// Plot::new()
230/// .with_options(PlotOptions {
231/// x_axis_label: "x".to_string(),
232/// y_axis_label: "F(x)".to_string(),
233/// plot_top: 0.5,
234/// title: "Binomial(100, 0.01) CDF".to_string(),
235/// ..Default::default()
236/// })
237/// .with_plottable(Line {
238/// x,
239/// y: y2,
240/// color: BLACK,
241/// ..Default::default()
242/// })
243/// .plot(&root)
244/// .unwrap();
245/// # use std::fs::rename;
246/// # drop(root);
247/// # rename(
248/// # format!("prob_plots.svg"),
249/// # format!("src/distribution/pois/doctest_out/prob_plots.svg"),
250/// # )
251/// # .unwrap();
252/// ```
253#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("prob_plots", "src/distribution/pois/doctest_out/prob_plots.svg")))]
254#[cfg_attr(
255 feature = "doc_outputs",
256 cfg_attr(all(), doc = "![Prob Plots][prob_plots]")
257)]
258///
259/// ```rust
260/// # use r2rs_nmath::{distribution::PoissonBuilder, traits::Distribution};
261/// # use strafe_type::FloatConstraint;
262/// assert_eq!(
263/// PoissonBuilder::new()
264/// .with_lambda(0)
265/// .build()
266/// .density(0)
267/// .unwrap(),
268/// 1.0
269/// );
270/// assert_eq!(
271/// PoissonBuilder::new()
272/// .with_lambda(0)
273/// .build()
274/// .probability(0, true)
275/// .unwrap(),
276/// 1.0
277/// );
278/// assert_eq!(
279/// PoissonBuilder::new()
280/// .with_lambda(0)
281/// .build()
282/// .quantile(1, true)
283/// .unwrap(),
284/// 0.0
285/// );
286/// ```
287pub struct Poisson {
288 lambda: Positive64,
289}
290
291impl Distribution for Poisson {
292 fn density<R: Into<Real64>>(&self, x: R) -> Real64 {
293 dpois(x, self.lambda, false)
294 }
295
296 fn log_density<R: Into<Real64>>(&self, x: R) -> Real64 {
297 dpois(x, self.lambda, true)
298 }
299
300 fn probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> Probability64 {
301 ppois(q, self.lambda, lower_tail)
302 }
303
304 fn log_probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> LogProbability64 {
305 log_ppois(q, self.lambda, lower_tail)
306 }
307
308 fn quantile<P: Into<Probability64>>(&self, p: P, lower_tail: bool) -> Real64 {
309 qpois(p, self.lambda, lower_tail)
310 }
311
312 fn log_quantile<LP: Into<LogProbability64>>(&self, p: LP, lower_tail: bool) -> Real64 {
313 log_qpois(p, self.lambda, lower_tail)
314 }
315
316 fn random_sample<R: RNG>(&self, rng: &mut R) -> Real64 {
317 rpois(self.lambda, rng)
318 }
319}
320
321pub struct PoissonBuilder {
322 lambda: Option<Positive64>,
323}
324
325impl PoissonBuilder {
326 pub fn new() -> Self {
327 Self { lambda: None }
328 }
329
330 pub fn with_lambda<P: Into<Positive64>>(&mut self, lambda: P) -> &mut Self {
331 self.lambda = Some(lambda.into());
332 self
333 }
334
335 pub fn build(&self) -> Poisson {
336 let lambda = self.lambda.unwrap_or(1.0.into());
337
338 Poisson { lambda }
339 }
340}
341
342#[cfg(test)]
343mod tests;
344
345#[cfg(all(test, feature = "enable_proptest"))]
346mod proptests;
347
348#[cfg(all(test, feature = "enable_covtest"))]
349mod covtests;